はじめに
「アジャイルソフトウェア開発の奥義」を読んでいるんですが,
デザインパターンを扱っており,それらの詳細をまとめたいと思います.
最初はActiveObjectパターンを題材にしていきます.
ActiveObjectパターンとは?
Commandパターンの利用方法の一つです.
マルチスレッドを実装する方法として古くから使われている手法で,
外部から受け取った非同期メッセージを自分の都合がよいタイミングで処理して,結果を返すことができるものです.
...言葉にしてもわかりづらいかもしれないですね.
ただ,簡素なマルチスレッドのコアを様々なシステムに組み込むため,様々な形に変形されて利用されています.
これは,ActiveObjectパターンは簡素なマルチスレッド処理であり,そのマルチスレッド処理を使うシステムによって,どのように並行処理を行うか(スレッドの使い方や管理方法など)が変わり,システムごとの要求に応じて,その基本の仕組みをうまく変形して利用しているということを表します.
そもそもCommandパターンとは?
Commandパターンは,Commandという概念をカプセル化することで,リクエストの発行者と受け手を分離するデザインパターンです.
これにより,コードの管理や拡張が容易になります.
...やはり言葉だけではわかりづらいので,以下のような例を交えながら説明します.
例えば,MotorOnCommandオブジェクト,MotorOffCommandオブジェクト,Sensorオブジェクト,Commandオブジェクトというものがあったとし,以下のような構造になっているものとします.
MotorOnCommand, MotorOffCommandのdo()を呼び出すと,
モーターをオン,オフにすることができます.
これにより,制御するCommandオブジェクトがどのようなものかを知らなくても,do()するだけでMotorを制御することができます.
Sensorオブジェクトも同様で,Sensor自身は自分が何をしているのかを知る必要がなく,イベントを検出したらdoするだけでSensorを制御することができます.
ActiveObjectパターンを利用したコード(Java)
実際にActiveObjectパターンを利用した振る舞いを見てみます.
「アジャイルソフトウェア開発の奥義」(p203)をベースにしています.
ActiveObjectEngineオブジェクトはコマンドオブジェクトのリンクリストを管理しています.
ユーザはこのエンジンオブジェクトに新しいコマンドを追加したり,
run()メソッドを呼び出します.
run()メソッドはリンクリストを最初から最後までたどりながら,
登録されているコマンドを実行し完了したコマンドをリストから外す仕様です.
import java.util.LinkedList;
public class ActiveObjectEngine {
LinkedList<Command> itsCommands = new LinkedList<>();
// 渡されたCommandオブジェクトをリストに追加
public void addCommand(Command c) {
itsCommands.add(c);
}
// コマンドリストが空でない限り処理を実行
public void run() {
while (!itsCommands.isEmpty()) {
Command c = itsCommands.getFirst();
itsCommands.removeFirst();
try {
c.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
インタフェースとなるCommandオブジェクトを用意します.
public interface Command
{
public void execute() throws Exception;
}
以下が実装されているSleepCommandです.
SleepCommandは最初にこのコマンドがすでに実行状態にあるかどうかをチェックします.
実行状態に入っていない場合はスタート時間を記録します.
実行状態に入っていて,指定された遅延時間を超えていない場合はActiveObjectEngineに戻します.
遅延時間を超えている場合は,wakeupコマンドをActiveObjectEngineに登録します.
public class SleepCommand implements Command{
private Command wakeupCommand = null;
private ActiveObjectEngine engine = null;
private long sleepTime = 0;
private long startTime = 0;
private boolean started = false;
public SleepCommand(long milliseconds, ActiveObjectEngine e, Command wakeupCommand)
{
sleepTime = milliseconds;
engine = e;
this.wakeupCommand = wakeupCommand;
}
public void execute() throws Exception
{
long currentTime = System.currentTimeMillis();
if(!started)
{
started = true;
startTime = currentTime;
engine.addCommand(this);
}
else if((currentTime - startTime) < sleepTime)
{
engine.addCommand(this);
}
else
{
engine.addCommand(wakeupCommand);
}
}
}
最後に,SleepCommandを利用した簡単なプログラムDelayedTyperを見ていきます.
Commandの振る舞いは繰り返し指定した遅延時間だけ待って,各キャラクタをプリントアウトし続け,ストップフラグがセットされるとループを抜けるといった振る舞いになります.
public class DelayedTyper implements Command{
private long itsDelay;
private char itsChar;
private static ActiveObjectEngine engine =
new ActiveObjectEngine();
private static boolean stop = false;
public static void main(String args[])
{
engine.addCommand(new DelayedTyper(100, '1'));
engine.addCommand(new DelayedTyper(100, '3'));
engine.addCommand(new DelayedTyper(100, '5'));
engine.addCommand(new DelayedTyper(100, '7'));
Command stopCommand = new Command()
{
public void execute(){stop = true;}
};
engine.addCommand(
new SleepCommand(20000, engine, stopCommand)
);
engine.run();
}
public DelayedTyper(long delay, char c)
{
itsDelay = delay;
itsChar = c;
}
public void execute() throws Exception
{
System.out.print(itsChar);
if(!stop)
delayedAndRepeat();
}
private void delayedAndRepeat() throws Exception
{
engine.addCommand(new SleepCommand(itsDelay, engine, this));
}
}
最後に
デザインパターンは初めて読んでみたんですが,ソフトウェア設計の工夫点がわかって特に苦も無く読み進めることができました.
ただ,こういったデザインパターンを実際にコーディングで活かすのは難しいんだろうなと感じます.
「きれいなソースコードを作る」といったことには常に興味があるので,こういったデザインパターンは知識だけでも要点を押さえておきたいです.