今回は、プログラム構造を基本設計におけるPAC(Presentation-Abstraction-Control)類型を参考にして構成し直してみました。ただし、Presentation要素とAbstraction要素が、結合機能により、(Control要素を介さない)直接的なつながりを持っているので、純粋にはPAC類型と呼べないかも知れません。
しかし、機能的なまとまりをPACエージェントとして構成し、プログラム全体をそれらPACエージェントの階層化された木構造で構成しているという点においては、PAC類型に準じたものです。PAC類型については、F. ブッシュマン、ほか著の『ソフトウェア アーキテクチャ ソフトウェア開発のためのパターン体系』(株式会社トッパン,1999)に説明されています。
PACエージェントとして構成するため、Javaのプログラムを2つに分けました。
まずは、依頼機側の接続エージェントとなるJavaのプログラムを示します。
/*
* ClientSideConnection.java
* The Client-Side Connection agent for a SCADA application.
*
* Created and modified:
* V 1.0.0 2008/07/31
*/
package client;
/**
* @author terra
*/
class ClientSideConnection {
private int i = -1;
/**
* Create and return pseudo SCADA data as if received from a server.
*/
double receiveScadaData() {
i += 1;
if (i >= 360) {
i = 0;
}
return (double) Math.sin(i * Math.PI / 180) * 100;
}
/**
* Main method for testing this class.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
ClientSideConnection scc = new ClientSideConnection();
for (int i = 0; i < 5; i++) {
System.out.println(scc.receiveScadaData());
}
}
}
次に、警報鳴動エージェントとなるJavaのプログラムを示します。
/*
* AlarmSounder.java
* The Temporary Alarm Sounder agent for a SCADA application.
* At some point, this agent will be rewritten in the JavaFX language.
*
* Created and modified:
* V 1.0.0 2008/08/11
*/
package client;
import java.applet.Applet;
import java.applet.AudioClip;
import javax.swing.JOptionPane;
/**
* @author terra
*/
class AlarmSounder {
private AudioClip ac = Applet.newAudioClip(
AlarmSounder.class.getResource("ALERT.WAV"));
/**
* Start sounding the alarm.
*/
public void startAlarm() {
ac.loop();
}
/**
* Stop sounding the alarm.
*/
public void stopAlarm() {
ac.stop();
}
/**
* Main method for testing this class.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
AlarmSounder as = new AlarmSounder();
as.startAlarm();
JOptionPane.showMessageDialog(null, "Alarm is sounding!",
"Alarm test", JOptionPane.WARNING_MESSAGE);
as.stopAlarm();
}
}
最後に、利用者インタフェース(UI)最上位エージェントである、UI枠付き窓エージェントのJavaFXプログラムを示します。
/*
* ScadaClientUI.fx
* The User Interface Frame (UIF) agent for a SCADA application.
*
* Created and modified:
* V 1.0.0 2008/08/01
*/
package client;
import javafx.animation.*;
import javafx.application.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import java.lang.System;
/**
* @author terra
*/
var csc = new ClientSideConnection(); // Client-Side Connection agent.
var alarmSounder = new AlarmSounder(); // Alarm Sounder agent.
/*
* The Abstraction compenent of the UIF agent.
*/
class UIFAbstraction {
attribute numericDatum: Number;
attribute numericData: Number[] = for (i in [0..180]) 0.0;
attribute alarting: Boolean;
function getDisplayColor(value: Number): Color {
var c: Color = Color.GREEN;
if (value > 90.0) {
c = Color.RED;
} else if (value > 75.0) {
c = Color.color(1.0, 0.6, 0.0);
}
return c;
}
function setNumericDatum(value: Number): Void {
numericDatum = value;
delete numericData[0];
insert value into numericData;
if (numericDatum > 90.0 and not alarting) {
alarting = true;
alarmSounder.startAlarm();
alarmDialog.toFront();
alarmDialog.visible = true;
}
}
function setAlarting(alarting: Boolean): Void {
this.alarting = alarting;
}
}
var uifa = UIFAbstraction {};
/*
* The Control compenent of the UIF agent.
*/
class UIFControl {
attribute schedular: Timeline = Timeline{
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time: 200ms
action: function(): Void {
uifa.setNumericDatum(csc.receiveScadaData());
}
}
}
}
var uifc = UIFControl {};
uifc.schedular.start();
/*
* The Presentation compenent of the UIF agent.
*/
Frame {
title: "SCADA UI Example"
width: 800, height: 632
visible: true
closeAction: function(): Void {System.exit(0);}
stage: Stage {
width: 800, height: 600
fill: Color.color(0.9, 0.9, 0.9)
content: [
Group {
translateX: 50, translateY: 50
content: [
Rectangle {
width: 55, height: 18
arcWidth: 4, arcHeight: 4
stroke: Color.BLUE, fill: Color.WHITE
},
Text {
x: 4, y: 5
textOrigin: TextOrigin.TOP
fill: bind uifa.getDisplayColor(uifa.numericDatum)
font: Font {
name: "Monospaced", size: 14, style: FontStyle.PLAIN
}
content: bind "{%7.2f uifa.numericDatum}"
},
Text {
x: 60, y: 6
textOrigin: TextOrigin.TOP
font: Font {
name: "Monospaced", size: 14, style: FontStyle.PLAIN
}
content: "m3/s"
}
]
},
Group {
translateX: 200, translateY: 50
content: [
Rectangle {
width: 60, height: 120
stroke: Color.BLUE, fill: Color.WHITE
},
Rectangle {
x: 10, y: 10
width: 10, height: 100
fill: LinearGradient {
startX: 0.0, startY: 0.0
endX: 0.0, endY: 1.0
stops: [
Stop {offset: 0.05, color: Color.RED},
Stop {offset: 0.0875, color: Color.color(1.0, 0.6, 0.0)},
Stop {offset: 0.125, color: Color.LIMEGREEN},
]
}
},
Rectangle {
x: 10, y: 10
width: 10, height: bind 50 - uifa.numericDatum / 2
fill: Color.BLACK
},
Line {
startX: 23, startY: 10
endX: 23, endY: 110
stroke: Color.BLACK
},
Group {
translateX: 23, translateY: 10
content: for (i in [-100..100 step 50]) Line {
translateY: i / 2 + 50
startX: 0, startY: 0
endX: 3, endY: 0
stroke: Color.BLACK
}
},
Group {
translateX: 32, translateY: 10
content: for (i in [-100..100 step 50]) Text {
translateY: 53 - i / 2
content: "{i}"
font: Font {
name: "Monospaced", size: 10, style: FontStyle.PLAIN
}
horizontalAlignment: HorizontalAlignment.LEADING
verticalAlignment: VerticalAlignment.TOP
}
}
]
},
Group {
translateX: 50, translateY: 200
content: [
Rectangle {
width: 230, height: 120
stroke: Color.BLUE, fill: Color.WHITE
},
Rectangle {
x: 10, y: 22
width: 181, height: 88
fill: Color.BLACK
},
Rectangle {
x: 10, y: 15
width: 181, height: 7
fill: Color.GRAY
},
Rectangle {
x: 10, y: 10
width: 181, height: 5
fill: Color.DARKGRAY
},
Line {
startX: 194, startY: 10
endX: 194, endY: 110
stroke: Color.BLACK
},
Group {
translateX: 194, translateY: 10
content: for (i in [-100..100 step 50]) Line {
translateY: i / 2 + 50
startX: 0, startY: 0
endX: 3, endY: 0
stroke: Color.BLACK
}
},
Group {
translateX: 203, translateY: 11
content: for (i in [-100..100 step 50]) Text {
translateY: 53 - i / 2
content: "{i}"
font: Font {
name: "Monospaced", size: 10, style: FontStyle.PLAIN
}
horizontalAlignment: HorizontalAlignment.LEADING
verticalAlignment: VerticalAlignment.TOP
}
},
Group {
translateX: 10, translateY: 10
content: for (i in [0..179]) Line {
translateX: i
startX: 0, startY: bind 50 - uifa.numericData[i] / 2
endX: 1, endY: bind 50 - uifa.numericData[i + 1] / 2
strokeWidth: 2
stroke: bind uifa.getDisplayColor(uifa.numericData[i + 1])
}
}
]
}
]
}
}
/*
* The Alarm Dialog agent.
*/
var alarmDialog = Dialog {
title: "Alarm"
width: 300, height: 60
visible: false
closeAction: function(): Void {
alarmSounder.stopAlarm();
uifa.alarting = false;
}
stage: Stage {
content: Text {
x: 10, y: 10
textOrigin: TextOrigin.TOP
fill: Color.RED
font: Font {size: 14}
content: "Flow rate exceeded the second upper limit!"
}
}
}
0 件のコメント:
コメントを投稿