デザインパターンMediatorについて勉強した
mediatorパターンとは
mediatorは仲介人の意味です。
あっちの人とそっちの人が、さらにこっちの人が自分勝手にお互いに指示を出し合っていては混乱が起こります。
それでは全員の考えをまとめて最適な指示を出すリーダーを一人用意しようというのがこのmediatorパターンです。
主にGUIを使ったプログラムに用いられるパターンのようです。
今回は簡単なパズルゲームを作ってみます。
8*8のパネルが並んでおり、そのうち一つのパネルをクリックするとクリックしたパネルとその4方のパネルがon/offでき、全てのパネルをonにするとクリアというあのゲームです。
コード例
Colleagueインタフェース
public interface Colleague { void setMediator(Mediator mediator); void setPosition(int x,int y); void setSelected(boolean select); boolean getSelected(); }
「あの人」や「この人」を表すインタフェースです。
mediatorパターンとして必要なのはsetMediatorメソッドただ一つです。
setMediatorメソッドは仲介人を保持するためのメソッドで、自身に変化があった場合は何も考えずにとりあえずmediatorに自身の変化を連絡します。
mediator側は連絡を受けると全体の状態を考慮して、残りのメソッド(ここではsetPosition、setSelected、getSelected)を駆使して各Colleagueに指示を出します。
setPositionメソッドはこの例固有のメソッドです。
パネルが8*8ありますが、自身がどこに位置しているのかを教えてもらうためのセッターです。
setSelectedメソッドはこの例固有のメソッドです。
クリックされた周囲のパネルはon/offを切り替える必要がありますが、このon/offを指示するのがこのメソッドです。
getSelectedメソッドはこの例固有のメソッドです。
現在のon/off状態を返します。
Mediatorインタフェース
public interface Mediator { void operating(int x,int y); }
仲介役を表すインタフェースです。
Colleagueは何か変化があった場合、「何か変化がありました」と仲介役に連絡する必要があります。
このメソッドはその連絡用です。
引数は自身の位置、つまり右上のパネルなのか中央のパネルなのかの情報です。
この引数もこの例固有のもので、引数がいらない場合や文字列を引数とする場合、operating2,operating3のように複数種類の連絡用メソッドを作ることもできます。
実装
Cellクラス
public class Cell extends JLabel implements Colleague { private Mediator mediator ; private boolean select; private int x; private int y; public Cell(Mediator mediator,int x,int y) { setMediator(mediator); setPosition(x,y); addMouseListener( new MouseListener() { @Override public void mouseClicked(MouseEvent e) { Cell.this.mediator.operating(x,y); } @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} }); setPreferredSize(new Dimension(50,50)); setHorizontalTextPosition(JLabel.CENTER); setSelected(false); } @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override public void setSelected(boolean select) { this.select = select; if(select) { setBackground(Color.WHITE); setText("O"); }else { setBackground(Color.LIGHT_GRAY); setText("X"); } } @Override public boolean getSelected() { return select; } @Override public void setPosition(int x, int y) { this.x = x; this.y = y; } }
Colleagueインタフェースを実装したクラスです。
medoatorフィールドは仲介役を保持、selectフィールドは現在のon/off状態を、x,yフィールドは自身のパネル座標をそれぞれ保持しておきます。
コンストラクタではMediatorと座標を受け取り、保持しています。
またパネルがクリックされたという情報が欲しいので、MouseListenerからクリックイベントを受け取っています。
パネルのサイズ、文字の位置、そしてデフォルトのon/off状態を設定しています。
setMediatorメソッドではmediatorを保持しています。
setSelectedメソッドではon/off状態を保持します。
それと同時に背景色とO/Xを表示します。
getSelectedメソッドは現在のon/off状態を返します。
setPositionメソッドはこのパネル自身の位置を保持します。
GamePanelクラス
public class GamePanel extends JPanel implements Mediator { private Colleague[][] cells ; private int width = 8; private int height = 8; public GamePanel() { setLayout(new GridLayout(width,height)); cells = new Colleague[width][height]; for(int i=0;i<cells.length;i++) { for(int j=0;j<cells[i].length;j++) { Cell cell = new Cell(this,i,j); cells[i][j] = cell; add(cell); } } } @Override public void operating(int x,int y) { turnover(x,y); turnover(x-1,y); turnover(x+1,y); turnover(x,y-1); turnover(x,y+1); } private void turnover(int x,int y) { if(x<0 || width<=x) return; if(y<0 || height<=y) return; cells[x][y].setSelected(!cells[x][y].getSelected()); } }
仲介役です。
仲介役はそれぞれのColleague達の状態を見てそれぞれに何をすべきかを指示していきます。
cellsフィールドはColleague達を保持するフィールドです。
while,heightフィールドはパネルが8*8であることを設定します。
コンストラクタでは8*8個のパネルを配置していきます。
operatingメソッドはColleagueから受ける連絡用メソッドです。
このメソッドが呼ばれると、引数の座標を中心に、4方向のパネルのon/offを反転します。
実際に反転できるか(パネルの範囲外かどうか)をチェックするのはturnoverメソッドの仕事です。
実行
実際に動かしてみましょう。
とは言ってもまだウィンドウを作っていないので、まずはそこからです。
DefaultFrameクラス
public class DefaultFrame extends JFrame implements WindowListener{ public DefaultFrame() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); addWindowListener(this); } @Override public void windowOpened(WindowEvent e) { pack(); setLocationRelativeTo(null); } @Override public void windowClosing(WindowEvent e) {} @Override public void windowClosed(WindowEvent e) {} @Override public void windowIconified(WindowEvent e) {} @Override public void windowDeiconified(WindowEvent e) {} @Override public void windowActivated(WindowEvent e) {} @Override public void windowDeactivated(WindowEvent e) {} }
簡単なコードなので説明は省略します。
public class Main { public Main() { JFrame frame = new DefaultFrame(); frame.add(new GamePanel()); frame.setVisible(true); } public static void main(String[] args) { new Main(); } }
特徴
主にGUIなどで、あちらこちらかでマウスやキーボードによる外部入力があるときに活躍するパターンです。
さらに複雑なプログラムだと仲介役の仲介役などが出てきたりしそうですね。
デザインパターンFacadeについて勉強した
facadeとは
正面、間口というような意味の英語です。
何か複雑な手順のコードがあったとします。
クラスやメソッドの呼び出し順にルールがあったり、何か複雑な計算だったりです。
全体でそのコードが一度しか登場しないとしても、コードの可読性を考慮するとその複雑な手順に名前をつけてメソッドとして一塊にしておくべきです。
そうすれば複雑なコードを読み解かなくても、メソッド名からこの部分は何をしているのかが読み取れます。
当然もう一度同じ処理をしたいならそのメソッド名一つを書くだけで良くなります。
このように複雑な(簡単でも)一連の手順をメソッドにまとめて、一つのメソッドを呼ぶだけで全てやってくれるようにすることをfacadeパターンと呼びます。
・・・そもそもこれはメソッドや関数の存在意義そのままではないでしょうか。
コード例
facadeパターンは要するにメソッドの実装のことだという認識をしたのでこれがfacadeパターンだ!というような例は思いつきませんでした。
今回は自分の生年月日を入力すると現在の年齢を教えてくれる窓口を作ってみます。
使用者であるMainクラスは、メソッドをたった一つ呼ぶことで、複雑なコードや内部で使われるメソッドのルールを全く意識せずに機能が実現できていることがわかるかと思います。
AgeFacadeクラス
public class AgeFacade { private AgeFacade() { } public static void agePrint() { Calendar now = Calendar.getInstance(); Calendar birthday = Calendar.getInstance(); int year = scanNumber("生まれた年を入力してください"); int month = scanNumber("生まれた月を入力してください"); int day = scanNumber("生まれた日を入力してください"); birthday.set(year,month,day); Calendar age = Calendar.getInstance(); age.setTimeInMillis(now.getTimeInMillis()-birthday.getTimeInMillis()); System.out.println(String.format( "あなたの年齢は%d歳と%dヶ月%d日です", age.get(Calendar.YEAR)-1970, age.get(Calendar.MONTH)+1, age.get(Calendar.DATE))); } private static int scanNumber(String message) { Scanner scanner = new Scanner(System.in); String text = ""; while(!text.matches("\\d+")) { System.out.print(message+":"); text = scanner.nextLine(); } return Integer.parseInt(text); } }
年齢を教えてくれる窓口です。
コンストラクタはprivateになっているので呼び出せません。
たった一つ、agePrintメソッドを呼び出すだけで全ての処理が完結します。
現在の時刻、入力する年月日の、それぞれCalendarクラスのインスタンスを作ります。
Calendarクラスはnewによるインスタンス化ができませんが、agePringメソッドの呼び出し元はそのようなルールは一切気にする必要はありません。
年月日の入力はscanNumberメソッドで行っています。
各カレンダーを使って計算し、現在の年齢を算出しています。
最後のprintlnでは年の値から1970引いています。
この1970引かなければいけないことも呼び出しもとは気にする必要はありません。
実装
public class Main { public static void main(String[] args) { AgeFacade.agePrint(); } }
agePrintという窓口へ行き、「私の年齢を教えてください」というだけで難しいことは何も考えずに年齢を知ることができます。
実行
生まれた年を入力してください:2000 生まれた月を入力してください:1 生まれた日を入力してください:2 あなたの年齢は21歳と5ヶ月21日です
特徴
特徴というかメソッドの使い方にもなりますが、プログラムは1つのメソッド(コンストラクタ)に全て書き込むべきではありません。
ほどほどの行数で分割して、何をしているかが一目でわかるような名前を付けるべきです。
まとめ
メソッドの存在するjava言語では基本中の基本となるパターンかと思います。
デザインパターンvisitorについて勉強した
visitorパターンとは
visitorは訪問者の意味です。
複数の情報が配列などでひとまとめにされているとき、そのデータに対する処理をデータと一緒のクラスに書いてしまえば簡単です。
しかし、その処理が複数パターンある場合、データと処理を別で管理してしまった方が見通しが良くなります。
例えば公園の遊具があるとします。
遊具ごとにどのような使い方、管理の仕方があるかは異なります。
ここに誰かがやってきたとき、その人は何をするでしょう。
子供なら遊ぶでしょうし、管理者であれば遊具の整備をします。
こう考えてみると滑り台は滑り台クラス、管理者は管理者クラスとして作った方が現実のイメージとつなげやすいのではないでしょうか。
遊具が、そこにきた人で遊ばせたり管理させたりするわけではないですから。
Visitorインタフェース
コード例
public interface Visitor { void visit(Swing swing); void visit(Slide slide); void visit(Bench bench); }
公園にある遊具を表すインタフェースです。
宣言されたメソッドは全てvisitメソッドで、オーバーロードしています。
この公園にはswing:ブランコ、slide:滑り台、bench:ベンチがあります。
このインタフェースを実装したのが子供クラスだったらvisit(Swing)メソッドでは何をするか、Slideだったら、Benchだったらと実装します。
もしこの公園に新たに鉄棒を追加したいなら、visitor(鉄棒)メソッドを追加してやることになります。
Playsetインタフェース
public interface Playset { void execute(Visitor visitor); }
遊具が実装するインタフェースです。
executeメソッドは訪問者を受け入れます。
ただし、その遊具が故障中であるなどした場合は受け入れないという分岐もできます。
実装
Swingクラス
public class Swing implements Playset { @Override public void execute(Visitor visitor) { visitor.visit(this); } }
ブランコのクラスです。
Playsetインタフェースを実装しており、executeメソッドをオーバライドしています。
executeメソッドでは訪問者を全て受け入れるようになっています。
visitorに対してvisitメソッドを呼び出しています。引数にはthisを渡していますね。
Visitorインタフェースにはvisitメソッドがあり、引数はSwing,Slide,Benchでした。
今このクラスはSwingクラスですから、visit(this)によって呼び出されるのはvisit(Swing)メソッドとなります。
そう考えるとVisitor.visit(Swing)メソッドにはブランコの使用方法を書けば良いことがわかりますね。
やることは同じなので、同じようにSlideクラス、Benchクラスのコードも載せていきます。
Slideクラス
public class Slide implements Playset { @Override public void execute(Visitor visitor) { visitor.visit(this); } }
Benchクラス>|java|
public class Bench implements Playset {
@Override
public void execute(Visitor visitor) {
visitor.visit(this);
}
}
|
Childクラス
public class Child implements Visitor { private String name ; public Child(String name) { this.name = name; } @Override public void visit(Swing swing) { System.out.println(String.format("%s君がブランコをこぐ",name)); } @Override public void visit(Slide slide) { System.out.println(String.format("%s君が滑り台を滑る",name)); } @Override public void visit(Bench bench) { System.out.println(String.format("%s君がベンチに座る",name)); } }
今度はVisitorインタフェースの実装クラスです。
childクラスは子供のクラスです。
nameフィールドはこの子供の名前を保持します。
コンストラクタ引数はnameフィールドに渡します。
visitメソッドは3つありますね。
それぞれSwing,Slide,Benchで何をするかを実装しています。
子供なのでどのように遊ぶかが書かれていますね。
Administratorクラス
public class Administrator implements Visitor { private String name ; public Administrator(String name) { this.name = name; } @Override public void visit(Swing swing) { System.out.println(String.format("%sさんは絡まっていたブランコの鎖を解いた",name)); } @Override public void visit(Slide slide) { System.out.println(String.format("%sさんは滑り台の手すりが壊れていないか確認した",name)); } @Override public void visit(Bench bench) { System.out.println(String.format("%sさんはベンチにペンキを塗った",name)); } }
Administratorは管理者の意味です。
基本的な実装はChildクラスと同じですが、visitメソッドの内容が異なります。
こちらは管理方法が書かれています。
Antクラス
public class Ant implements Visitor { @Override public void visit(Swing swing) { System.out.println("アリさんは巣にごはんを持ち帰っているところだ"); } @Override public void visit(Slide slide) { System.out.println("アリさんは巣にごはんを持ち帰っているところだ"); } @Override public void visit(Bench bench) { System.out.println("アリさんは巣にごはんを持ち帰っているところだ"); } }
ついでにアリさんのクラスも作ってみました。
アリさんは遊具には目もくれず今日を一所懸命生きているようですね。
実行
public class Main { public Main() { Playset[] playsets = new Playset[] { new Swing(), new Slide(), new Bench(), }; Visitor[] visitors = new Visitor[] { new Child("山田"), new Administrator("竹本"), new Ant(), }; for(Visitor v : visitors) { for(Playset p : playsets) { p.execute(v); } } } public static void main(String[] args) { new Main(); } }
各クラスのインスタンスを作り、forループを使って遊具と訪問者全ての組み合わせでexecuteメソッドを呼び出しています。
山田君がブランコをこぐ 山田君が滑り台を滑る 山田君がベンチに座る 竹本さんは絡まっていたブランコの鎖を解いた 竹本さんは滑り台の手すりが壊れていないか確認した 竹本さんはベンチにペンキを塗った アリさんは巣にごはんを持ち帰っているところだ アリさんは巣にごはんを持ち帰っているところだ アリさんは巣にごはんを持ち帰っているところだ
特徴
データと処理を分割して管理するために便利なパターンです。
新たに処理(訪問者)を増やしたければVisitorインタフェースを実装した新たなクラスを作るだけで済みます。
反対にデータ(遊具)を増やすにはVisitorインタフェースを実装した全てのクラスを書き換えることになるので、少々手間がかかります。
補足
後から処理を追加するときに威力を発揮するパターンですが、一つだけ注意すべき点があります。
データ側がどこまで自身の情報を公開し、外部からの編集を許可するかを先に決めておく必要があることです。
例では管理人がベンチにペンキを塗っていましたが、ペンキの色までは指定していませんでした。(そもそも例では色という概念すらありませんでした)
後からベンチの色を指定したくなった場合はBenchクラスを変更しなければいけなくなります。
既存のクラスはなるべく変更しないためにも、なるべく後の拡張性まで考えてクラスを作ることを心がけたいところです。
デザインパターンstrategyについて勉強した
strategyパターンとは
戦略などのような意味です。
プログラミングで言えば「アルゴリズム」もstrategyと捉えることもできます。
ある問題を解くときに、方法Aと方法Bがあったとします。
AとBどちらを使えば効率がいいか試してみるとき、両方作って別のクラスとして管理するのではないかと思います。
Aのコードを試した後にコードを全て消してBを書き直すなんてことはしませんね。
このような、AとBを両方作っていつでも切り替えができるようにするデザインパターンをstrategyパターンと呼びます。
コード例
ランダムな数列を昇順に並べ替えるプログラムを例にします。
Sorterインタフェース
public interface Sorter { int[] sort(int[] list) ; }
後述する全てのソート方法は、このインタフェースを実装させます。
sortメソッドは引数に渡された数列をソートして返すように宣言しています。
サブクラスが実装します。
実装
BublleSortクラス
public class BublleSort implements Sorter { @Override public int[] sort(int[] list) { int[] newlist = list.clone(); int s ; for(int i=1;i<newlist.length;i++) { for(int j=i;j<newlist.length;j++) { if(newlist[j-1]>newlist[j]) { s = newlist[j-1]; newlist[j-1] = newlist[j]; newlist[j] = s; } } } return newlist; } }
ソート方法の一つである、バブルソートを実装したクラスです。
※ソートの知識が浅いため、ここではこのバブルソートだけを実装例とします。
StrategySorterクラス
public class StrategySorter { public StrategySorter(Sorter sorter) { int[] list = new int[99999]; Random random = new Random(); for(int i=0;i<list.length;i++) { list[i] = random.nextInt(); } System.out.println("start"); long time = System.currentTimeMillis(); sorter.sort(list); System.out.println(System.currentTimeMillis()-time); } }
コンストラクタの引数から渡されるSorterを使って数列をソートします。
Sorterはインタフェースであり、実際に渡されてくるクラスが何であるのかはわかりません。
このクラスは実際に使われているのがどんなアルゴリズムであるのかを意識しなくても良いように書かれています。
list変数はランダムな数列が入れられます。
random変数はランダム数列生成期です。
次のforループではランダム数列を作っています。
コンソールに"start"を表示し、現在の時間をミリ秒で記憶します。
sorter引数からsortメソッドを呼び出し、ソートを開始します。
ソートが終了すると、この時点の時間をミリ秒で確認し、先ほど記憶したスタート時間を引き算します。
この計算された時間がソートにかかった時間となります。
実行
public class Main { public static void main(String[] args) { new StrategySorter(new BublleSort()); } }
実行します。
mainメソッドではコンストラクタの引数にBublleSortクラスのインスタンスを渡しています。
これで先ほど作ったバブルソートのアルゴリズムを選択したことになります。
特徴
状態を切り替えるstateパターンにも似ていますが、あちらは状態の切り替えを前提とするパターン(頻繁に切り替えている)のに対して、strategyパターンは試しに検証してみるような場面で使われたりします。
デザインパターンdecoratorについて勉強した
Decoratorパターンとは
オブジェクトをデコレーションしていくようなデザインパターンです。
ケーキのデコレーションや商品のラッピングのようにあるオブジェクトを中心に同じ型の別のオブジェクトでデコレーションしていきます。
コード例
ある言葉を持つオブジェクトを、別の言葉を持つオブジェクトでデコレーションしていきます。
具体的には「カー(車)」を「スーパー」でデコレーションして「スーパーカー」にします。
Itemクラス
public abstract class Item { private Item item ; public void setItem(Item item) { this.item = item; } protected abstract String getMyText() ; @Override public String toString() { String newtext = getMyText(); if(item!=null) { newtext += item.toString(); } return newtext; } }
このパターンのメインとなるクラスです。
このクラスを継承するサブクラスが、デコレーションされるオブジェクトとなり、またデコレーションする側のオブジェクトになります。
itemフィールドはデコレーションされるオブジェクトを保持します。
setItemメソッドはitemフィールドのセッターです。
getMyTextメソッドは自身の持つ文字列を返すように実装します。
Carクラスは「カー」を、Superクラスは「スーパー」を返します。
toStringメソッドはデコレーション後の文字列を返します。
itemがnullの場合、つまり自分がもっともデコレーションされる中心である場合は自身の持つ文字列だけが返ります。
実装
WooperLooperクラス
public class WooperLooper extends Item { public WooperLooper() { } public WooperLooper(Item item) { setItem(item); } @Override protected String getMyText() { return "ウーパールーパー"; } }
Itemのサブクラスの一つです。
引数なしのコンストラクタが呼ばれた場合はこのインスタンスが中心となるオブジェクトとなります。
引数ありのコンストラクタの場合は引数のitemをデコレーションする側となります。
getMyTextメソッドが返すのは「ウーパールーパー」です。
Superクラス
public class Super extends Item { public Super() { } public Super(Item item) { setItem(item); } @Override protected String getMyText() { return "スーパー"; } }
クラス名が少し紛らわしいですが継承元のクラスという意味ではありません。
文字列「スーパー」を持つクラスです。
内容はウーパールーパーとほとんど同じです。
Carクラス
public class Car extends Item { public Car() { } public Car(Item item) { setItem(item); } @Override protected String getMyText() { return "カー"; } }
Perkerクラス
public class Perker extends Item { public Perker() { } public Perker(Item item) { setItem(item); } @Override protected String getMyText() { return "パーカー"; } }
Mainクラス
public class Main { public Main() { System.out.println(new WooperLooper()); System.out.println(new WooperLooper(new Super(new Car(new Perker())))); System.out.println(new Super(new WooperLooper())); System.out.println(new WooperLooper(new Car())); } public static void main(String[] args) { new Main(); } }
実行
さあ実行しましょう。
ウーパールーパー ウーパールーパースーパーカーパーカー スーパーウーパールーパー ウーパールーパーカー
特徴
デコレーションする側とされる側が同じ型であるため、組み合わせは無限に存在します。
compositeパターンにも似ていますね。
compositeパターンは収納のイメージでしたが、こちらは飾りつけのようなイメージになります。
1種類しかない代わりにたくさん入るcompositeか、たくさんの種類がある代わりに1つしか入らないdecoratorです。
もちろん、両方のいいとこ取りをした、たくさんの種類があり、たくさん入るようなクラスも作ることができますね。
デザインパターンabstract factoryについて勉強した
abstract factoryパターンとは
訳すと抽象的な工場です。
抽象クラスのコンストラクタを作り、その抽象的なコンストラクタからメソッドを呼び出す(ように見える)コードパターンです。
実際には抽象クラスを継承した具象クラスは存在しますが、表面的には見えにくいようにコードを書きます。
コード例
ジュース工場を例にします。
何を作るかはまだ決まっていない「抽象ジュース工場」、なんの味かはまだ決まっていない「抽象ジュース」が登場します。
JuiceFactoryクラス
public abstract class JuiceFactory { private static AppleJuiceFactory apple = new AppleJuiceFactory(); private static OrangeJuiceFactory orange = new OrangeJuiceFactory(); private static BananaJuiceFactory banana = new BananaJuiceFactory(); public static JuiceFactory createFactory(String name) { switch(name) { case "りんご": return apple; case "オレンジ": return orange; case "バナナ": return banana; } return null; } public abstract Juice buy() ; public abstract void saleCount() ; }
抽象ジュース工場です。
3つのフィールドは具象ジュース工場の候補です。
createFactoryメソッドでは渡された文字列からジュース工場を選択して返します。
これがコンストラクタの代わりとなります。
つまり、「りんご」という文字列を渡すことでAppleJuiceFactoryクラス(後述)のインスタンスが得られるわけです。
何が嬉しいのかはもう少し後で説明します。
buyメソッドはこの工場でジュースを購入するメソッドです。
サブクラスで実装します。
saleCountメソッドはこの工場で購入されたジュースの数をコンソールに出力します。
サブクラスで実装します。
Juiceインタフェース
public interface Juice { void taste(); }
抽象ジュースです。
tasteメソッドはジュースの味をコンソール出力します。
サブクラスで実装します。
以上が抽象ジュース工場と抽象ジュースです。
この工場を使用する側が気にするのはここまでです。
実装
ここからは具象ジュース工場と具象ジュースを実装していきます。
AppleJuiceFactoryクラス
public class AppleJuiceFactory extends JuiceFactory { private int count = 0; @Override public Juice buy() { count++; return new AppleJuice(); } @Override public void saleCount() { System.out.println(String.format("うちのリンゴジュースは%d個売れています",count)); } }
リンゴジュース工場です。
countフィールドは今まで売れたジュースの数を保持します。
buyメソッドはこの工場からジュースを購入するメソッドでした。
ジュースの販売数を1増やしてAppleJuiceを返しています。
saleCountメソッドは売れたジュースの数をコンソール出力します。
AppleJuiceクラス
public class AppleJuice implements Juice { @Override public void taste() { System.out.println("みずみずしいリンゴジュース"); } }
リンゴジュースです。
tasteメソッドはジュースの味をコンソール出力します。
以降のオレンジジュース、バナナジュースもリンゴジュースと同じように実装していきます。
OrangeJuiceFactoryクラス
public class OrangeJuiceFactory extends JuiceFactory { private int count = 0; @Override public Juice buy() { count++; return new OrangeJuice(); } @Override public void saleCount() { System.out.println(String.format("うちのオレンジジュースは%d個売れています",count)); } }
オレンジジュース工場
OrangeJuiceクラス
public class OrangeJuice implements Juice { @Override public void taste() { System.out.println("甘酸っぱいオレンジジュース"); } }
オレンジジュース
BananaJuiceFactoryクラス
public class BananaJuiceFactory extends JuiceFactory { private int count = 0; @Override public Juice buy() { count++; return new BananaJuice(); } @Override public void saleCount() { System.out.println(String.format("うちのバナナジュースは%d個売れています",count)); } }
バナナジュース工場
BananaJuiceクラス
public class BananaJuice implements Juice { @Override public void taste() { System.out.println("バナナジュース"); } }
バナナジュース
実行
public class Main { public Main() { JuiceFactory factory ; Scanner scanner = new Scanner(System.in); String text ; while(true) { System.out.println("********"); System.out.print("ジュースを選んでください(りんご,オレンジ,バナナ):"); text = scanner.nextLine(); if(text.equals("exit")) break; factory = JuiceFactory.createFactory(text); if(factory==null) continue; factory.saleCount(); while(true) { System.out.print("ジュースを何個書いますか:"); text = scanner.nextLine(); if(!text.matches("\\d{1,3}")) continue; break; } for(int i=Integer.parseInt(text);i>0;i--) { factory.buy().taste(); } } scanner.close(); } public static void main(String[] args) { new Main(); } }
実行すると、コンソールにりんご、オレンジ、バナナのいずれかを入力するように求められます。
もしくはexitを入力するとプログラムを終了します。
new Scanner(System.in)はコンソールからの入力を要求するためのインスタンスを生成しています。
このインスタンスのnextLineメソッドを呼ぶことで、コンソールが入力待ち状態になります。
りんご、オレンジ、バナナのいずれかを正しく入力しないと、JuiceFactory.createFactoryメソッドは該当するクラス(ジュース工場)が存在しないということでnullを返します。
工場を選択すると、工場のオーナーが現在の売れ行きを教えてくれます。
2段目のwhileではジュースの購入数を入力します。
購入する数を入力するとその分だけジュースを飲むことができます。
ちなみに、if(!text.matches("\\d{1,3}")) continue;の部分は、コンソールから入力された文字列が全て半角数字で入力されているかをチェックする正規表現です。正しくなければループの先頭に戻され、もう一度購入数を聞かれます。
特徴
例の通り、ユーザからの入力によってクラスを選択するような場面で使用されます。
外部からの文字列入力によって使用するクラスが決定できるので、クラスを選択するという点では
JuiceFactoryクラスのサブクラスを作ってもcreateFactoryメソッドを書き換えなければこのパターンとしては使えません。
この例ではcreateFactoryメソッド内でnew AppleJuiceFactoryのようにnew をしていましたが、他にもClass.getConstructorメソッドを使った方法もあるようです。
補足
今回は日本語入力のみに対応するプログラムにしましたが、英語や絵文字、色名による入力などにも対応させれば、より柔軟なユーザビリティが得られるようになるかもしれません。
デザインパターンFactoryMethodについて勉強した
Factory Methodパターンとは
factoryは工場のことです。
インスタンスを次々作り出す工場のイメージでしょうか。
以前にTemplate Methodパターンについて説明しましたが、それのコンストラクタ版といったところです。
chiopino.hatenablog.com
メインプログラムではインスタンスを直接呼び出さないので、型に柔軟なプログラムを書くことができます。
パターンの例
新入社員に社員証を発行し、出勤、退勤を管理するシステムを作ります。
例ではフレームワークとなる2つの抽象クラス、それを実装した2クラス、テスト用の1クラスを作ります。
Productインタフェース
public interface Product { void on(); void off(); }
社員証の元となるインタフェースです。
二つのメソッドが宣言されていますが、これらは「何らかの機能を実装する」ためのメソッドです。
社員証なら「出勤」「退勤」を実装できますし、
pcなら「電源オン」「電源オフ」を実装できます。
このon,offメソッドはFactoryMethodパターンからすると必要のないものです。
極端に言ってしまえば製品を意味するProductインタフェースさえあれば、メソッドは何もなくてもFactoryMethodを名乗れます。
ただし実用性があるかは別の話です。
Factoryクラス
public abstract class Factory { public abstract Product createProduct(String name) ; }
createProductメソッドはProduct(製品)を生産するための抽象メソッドです。
実装例
これら2つのクラス、インタフェースを実装して、実際の社員証と管理システムを作っていきます。
EmployeelIDcardクラス
public class EmployeelIDcard implements Product { private String name ; public EmployeelIDcard(String name) { this.name = name; System.out.println(name + "が入社しました"); } public void on() { System.out.println(name + "が出勤しました"); } public void off() { System.out.println(name + "が退勤しました"); } }
コンストラクタでは持ち主の名前を社員証に登録しています。
登録が完了したら新入社員の名前と、入社したことをアナウンスします。
onメソッドはProductインタフェースで宣言したメソッドです。
ここでは出勤という意味を持たせて実装しました。
本来であればAdapterパターンでメソッド名をarrival(出社)のように変えたいところですが、FactoryMethodパターンの説明には不要であるためそのままonメソッドとします。
offメソッドはonメソッドと同様の実装となっています。
EmployeelIDSystemクラス
public class EmployeelIDSystem extends Factory { @Override public Product createProduct(String name) { return new EmployeelIDcard(name); } }
社員管理システムです。
管理システムとは言ってもここでは社員証の発行しかしていません。
createProductメソッドはFactoryクラスで宣言されていた抽象メソッドでした。
EmployeelIDcardクラスのインスタンスを生成し、それを返すだけのメソッドです。
テスト用クラス
public class Main { public Main() { Factory factory = new EmployeelIDSystem(); Product a_san = factory.createProduct("安藤"); Product b_san = factory.createProduct("馬場"); Product c_san = factory.createProduct("千代田"); Product d_san = factory.createProduct("大和"); System.out.println("----------------"); a_san.on(); b_san.on(); c_san.on(); d_san.on(); System.out.println("----------------"); a_san.off(); b_san.off(); c_san.off(); d_san.off(); } public static void main(String[] args) { new Main(); } }
4人の新入社員にそれぞれの社員証を発行しています。
createProductメソッドは、渡された名前でProductクラスのスーパークラスのインスタンスを生成して返すものでした。
変数a_san,b_san,c_san,d_sanにはそれぞれの社員証が保持されました。
それぞれの社員証をon,offメソッドを呼び出しています。
onメソッドは出勤、offメソッドは退勤でした。
実行してみます。
安藤が入社しました 馬場が入社しました 千代田が入社しました 大和が入社しました ---------------- 安藤が出勤しました 馬場が出勤しました 千代田が出勤しました 大和が出勤しました ---------------- 安藤が退勤しました 馬場が退勤しました 千代田が退勤しました 大和が退勤しました
使用する利点
createProductの実装で、社員証を全てコレクションに追加することで全社員を管理することができたりします。
コンストラクタ呼び出しとセットにしたい処理がある場合に力を発揮するパターンです。
また、createProductで呼び出しているコンストラクタをGuestIDcardなどに差し替えることで、簡単に来客用IDカード用のシステムに変更したりもできます。
まとめ
FactoryクラスやProductインタフェースがFactoryMethodパターンの本体であり、EmployeelIDcardクラスやEmployeelIDcardSystemクラスはあくまで実装例に過ぎません。
FactoryクラスやProductインタフェースだけ見ると、社員証システムのプログラムには到底見えません。
なぜなら社員証システム以外でも十分使用できるように設計しているからです。
テレビやスマホ、車などでも実装次第です。
デザインパターンはプログラムの部品化、そのやり方であることを忘れないようにしましょう。