続 デザインパターン入門 1. はじめに 前回 デザインパターンとは何か? を学びました なんか難しそう とか エラい人 のためのものでしょ? って思う人も多いかもしれませんが 実はそんなことないですよ ー という話もしましたね デザインパターンとは プログラムのお手本集 であり 実は知らず知らずのうちに使っ ているもの オブジェクト指向の話そのもののパターンもある といったお話もしました また コーディングの経験不足を補うもの であることもお話しました それからデザインパターンがいつごろ誕生したか 2000 年です Java の誕生と同じタイミ ングです そのとき 基本の 23 パターンを提唱したのが GoF ( Gang Of Four, 四人組 ) といわれる外国人 4 人だという話もしました 今回は 23 のうちから代表的な 3 つに絞って より詳しく見ていきましょう 2. 詳しく見る 1-Singleton パターン Singleton パターンは並み居るパターンのうちでももっともシンプルかつよく使われるパ ターンです 通常 クラスからインスタンスを作成する際に new をしてインスタンスを得ますが これ だとインスタンスがいくつでも作れます 普通はそれでかまわないんですが 何回作ってもまったく同じインスタンス という場合 もあります であれば 何個も作らずに 1 個だけ作って使いまわした方が効率がいいです ね? この仕組みをカタログ化したのが Singleton パターンです 実装面で言うと まず パターンを適用したいクラスに そのクラスのインスタンスを保持する static なフィールドを設けます さらに そのクラスのインスタンスを得るための static なメソッドを実装します 多くの場合 このメソッドには getinstance() の名前が付きます - 1 -
getinstance() がやる内容は 前述の static フィールドの値を返却する ただそれだけです コーディング例を挙げます public class SomeClass { private static SomeClass someclass = new SomeClass(); public static SomeClass getinstance() { return someclass; こうすると この SomeClass クラスのインスタンスは getinstance() メソッドを呼ぶことで 得ることができます 返ってくるインスタンスは常にこのクラスが static に保持している インスタンスですので 無駄にインスタンスを作らなくて済みますね でも 実はこのままだと この getinstance() を呼ばなくても new でインスタンスが作れち ゃいます みんながみんなこのメソッドを使ってくれるとは限らないですから これだけ だと効果が薄いんですね そこで new できないように コンストラクタを private にしちゃいます public class SomeClass { private SomeClass() { private static SomeClass someclass = new SomeClass(); public static SomeClass getinstance() { return someclass; この状態だと SomeClass を new しようとした途端 Eclipse に コンストラクター SomeClass は不可視です とコンパイルエラーを突きつけられます - 2 -
でもちょっとまって! インスタンスが 1 個しか必要ないなら 単にメソッドを全て static にしてしまって インスタンス化せずに利用できるクラスにすればいいのでは? そう思ったあなた 賢いです でも ちゃんと Singleton パターンを適用するのには理由があるんです (1) static にするとメソッドのオーバーライドができなくなってしまう ( ア ) 拡張されたクラスを作りたくても作れない ( イ ) テストするときにスタブが作りにくい (2) static にするとインスタンスを複数にできない (Singleton はインスタンスが1つとは限らない ) つまり拡張性や保守性を考えれば static で実装するよりも Singleton パターンで実装した 方がよいということです ところで余談ですが 先の例では static フィールド someclass に初期値として SomeClass クラスのインスタンスを設定していましたが 実は null で初期化しておいて getinstance() の中でインスタンス化するほうが一般的です 次のようなコードになります public class SomeClass { private SomeClass() { private static SomeClass someclass = null; public static SomeClass getinstance() { if (someclass == null) { someclass = new SomeClass(); return someclass; getinstance の中で someclass が null かどうか判定し null の場合はインスタンス化を行っています こうするのには理由があります この例ではこのクラスはとてもシンプルですが もっと複雑なクラスだった場合 あるいはインスタンス化の手続きが重い ( 時間のかかる処理がたくさんある ) 場合だと このクラスが参照された瞬間にインスタンス化が実行されてしまうのはよくないからです 利用するときになって初めてインスタンス化が行われたほうが マシンにかかる負荷の点でも 起動時に利用者を待たせるかもしれない可能性を考えても良いということになります - 3 -
問題 (1) 内部に StringBuilder のインスタンスを保持していて文章生成を行うクラス SenetenceGenerator を用意しました またそのクラスを利用して文章を生成する main メソッドを持つクラス Main も用意しました しかし 現状は結果として空文字列が表示されてしまいます Singleton パターンを適用して 正しく文章が表示されるようにしましょう 3. 詳しく見る 2-Iterator パターン Iterator パターンは JDK に Iterator というインタフェースがあるので Java のみな さんおなじみかと思います.NET の方なら Enumerator といえばおわかりいただけ るでしょうか とりあえず例を見て見ましょう 以下のような おもちゃ を表現するための Toy ク ラスがあるとします package toybox.easyversion; public class Toy { private String name; public Toy(String name) { this.name = name; public String getname() { return name; - 4 -
この Toy クラスのインスタンスの集合体である ToyBox クラスがあるとします package toybox.easyversion; public class ToyBox { private Toy[] toys = new Toy[256]; private int last = 0; public Toy get(int index) { return toys[index]; public void add(toy toy) { this.toys[last] = toy; last++; public int getlength() { return last; public ToyBoxIterator iterator() { ToyBoxIterator it = new ToyBoxIterator(this); return it; このおもちゃ箱クラスは 256 個までおもちゃインスタンスを格納できます なお 簡単 のため判定処理が入っていないので 257 個めを入れようとするとエラーになります そしてこのクラスには反復子 ( イテレータ ) のインスタンスを得るためのメソッド iterator() があります 反復子は以下のようなメソッドで構成されます hasnext() メソッド 次のオブジェクトが存在するか否かを boolean 値で返す next() メソッド 次のオブジェクトを取得しつつ インデックスを前に進める - 5 -
package toybox.easyversion; public class ToyBoxIterator { private ToyBox toybox; private int index; public ToyBoxIterator(ToyBox toybox) { this.toybox = toybox; this.index = 0; public boolean hasnext() { boolean result = index < toybox.getlength(); return result; public Toy next() { Toy toy = toybox.get(index); index++; return toy; またフィールドとしておもちゃ箱インスタンスを保持します hasnext() や next() のメ ソッドはこのインスタンスについて次の有無を確認したり取り出して返したりします このクラスを群を利用して複数のおもちゃインスタンスを管理すると 次のようなコー ドになります package toybox.easyversion; public class Main { public static void main(string[] args) { // おもちゃをおもちゃ箱に 4 つ格納 ToyBox toybox = new ToyBox(); toybox.add(new Toy(" ヨーヨー ")); toybox.add(new Toy(" おはじき ")); toybox.add(new Toy(" ぬいぐるみ ")); toybox.add(new Toy(" 剣玉 ")); // おもちゃ箱インスタンスからおもちゃ箱反復子 ( イテレータ ) を得る ToyBoxIterator it = toybox.iterator(); // 次がある間取り出して名前を表示する処理を繰り返す while (it.hasnext()) { Toy toy = it.next(); System.out.println(toy.getName()); - 6 -
こうしておくと この繰り返し処理は ToyBox クラスの実装に依存しなくなる つまり ToyBox クラスをどんなに変更しようが この繰り返し部分の変更が不要となります 問題 Toy インスタンスを配列ではなく List で管理するように ToyBox クラスと ToyBoxIterator クラスの実装を変更し 繰り返し処理に変更が必要ないことを確認しましょう さて 上記に示した例は簡単のため シンプルに書きましたが 実際にはもっと汎用性を高めるため インタフェースを使用して表現するのが普通です まず 集合体となるクラス 上記の例では ToyBox クラスですが が iterator() メソッドを持つようにインタフェースを用意し ToyBox クラスに実装させます package toybox; public interface Aggregate { public abstract Iterator iterator(); package toybox; public class ToyBox implements Aggregate { private Toy[] toys = new Toy[256]; private int last = 0; public Toy get(int index) { return toys[index]; public void add(toy toy) { this.toys[last] = toy; last++; public int getlength() { return last; public Iterator iterator() { Iterator it = new ToyBoxIterator(this); return it; - 7 -
Aggregate とは 集合体 という意味です 何かの集合体であるクラスにはこのインタフェースを実装させる というルールにすれば必然的に Iterator を返すような iterator() メソッドを実装することになります 次に Iterator インタフェースを用意して反復子クラス 例では ToyBoxIterator クラスです に 実装させます Iterator インタフェースはメソッドとして next() と hasnext() を実装することを要求します package toybox; public interface Iterator { public abstract boolean hasnext(); public abstract Object next(); package toybox; public class ToyBoxIterator implements Iterator { private ToyBox toybox; private int index; public ToyBoxIterator(ToyBox toybox) { this.toybox = toybox; this.index = 0; public boolean hasnext() { boolean result = false; if (index < toybox.getlength()) { result = true; return result; public Object next() { Toy toy = toybox.get(index); index++; return toy; このようにすることで 例えばこれが ToyBox クラスではなく全然別な集合体を扱う場合でも 同じように扱うことができます - 8 -
main のクラスは次のようになります package toybox; public class Main { public static void main(string[] args) { // おもちゃをおもちゃ箱に 4 つ格納 ToyBox toybox = new ToyBox(); toybox.add(new Toy(" ヨーヨー ")); toybox.add(new Toy(" おはじき ")); toybox.add(new Toy(" ぬいぐるみ ")); toybox.add(new Toy(" 剣玉 ")); // おもちゃ箱インスタンスから反復子 ( イテレータ ) を得る Iterator it = toybox.iterator(); // 次がある間取り出して名前を表示する処理を繰り返す while (it.hasnext()) { Toy toy = (Toy)it.next(); System.out.println(toy.getName()); ToyBox クラスをインスタンス化して Toy インスタンスを詰め込むところは一緒です next() メソッドが汎用化のために戻り値の方が Object になったことで キャストが必要になってしまいましたがあとは一緒です 宿題 実際にこのクラスを作成しましょう 実は続きがあります 汎用化したものをさらに使いやすくするため JDK5.0 以降に導入されている ジェネリクス宣言 を使うと 集合体に何のクラスのインスタンスが入るのかを指定することができるようになります 普段 Java や.NET でコーディングしていると List<String> だのと書くこともあると思いますが あれです ここでは基本をおさえるということで そこまではやりませんが頭の片隅にいれておいていただけるといいかと思います - 9 -
4. 詳しく見る 3-FlyWeight パターン 3 つめは FlyWeight パターンを見て見ましょう ( 前回とまったく同じでは面白くないので :-p) FlyWeight というのはボクシングでいう フライ級 のことです コーディングにおいてはなにをフライ級にする (= 減量する ) かというと メモリに保持するインスタンスです 例を見て見ましょう /** * インスタンスを 1 つ作るのにとても時間とメモリを食うクラス */ public class HeavyObject { private Object heavydata; public Object getheavydata() { return heavydata; /** * コンストラクタ 引数を元にして フィールドの値を設定する * @param seed */ public HeavyObject(String seed) { this.heavydata = generateheavydata(seed); /** * 何か重い処理をして メモリを食うような重い結果を生成して返すメソッド * @param seed * @return */ private Object generateheavydata(string seed) { // ここで何か重い処理をして メモリを食うような重い結果を生成 try { Thread.sleep(1000); catch (InterruptedException e) { // 重い物を返す ( イメージ ) return seed; 上記は インスタンスを作るのにすごく時間がかかって かつメモリを大量に消費するクラスです 簡単のために 重い重い といいつつシンプルな処理になっていて 時間がかかるといっても sleep しているだけですが ( 笑 ) 実際の現場の開発では本当に時間とメモリを消費するようなクラスは存在します 例えば計算に時間がかって かつ結果データがすごく大きい などです - 10 -
このクラスのインスタンスをいくつか作る処理があるとします public static void main(string[] args) { HeavyObject objt = new HeavyObject("T"); HeavyObject objh = new HeavyObject("H"); HeavyObject obji = new HeavyObject("I"); HeavyObject objs = new HeavyObject("S"); HeavyObject obji2 = new HeavyObject("I"); HeavyObject objs2 = new HeavyObject("S"); HeavyObject obja = new HeavyObject("A"); HeavyObject objp = new HeavyObject("P"); HeavyObject obje = new HeavyObject("E"); HeavyObject objn = new HeavyObject("N"); System.out.println(objI); System.out.println(objI2); アルファベットを 1 文字ずつコンストラクタに渡し 10 個のインスタンスを作っています 1 個のインスタンスを作るのに 1 秒かかるとすると 合計 10 秒かかります また 1 個のインスタンスがメモリを 1 メガバイト消費するとすると 合計 10 メガバイトのメモリが消費されます 最後に変数 obji と obji2 を表示していますが コンストラクタに渡す引数は同じでも当然違うインスタンスなので 違う値が表示されます 出力例 mysample.heavyobject@26db62 mysample.heavyobject@10d0630 しかし obji と obji2 が持っている内部データはどう考えても同じのはずです だから インスタンスを 2 つ作ってしまうのはムダ では次のようなコードだったらどうでしょう? - 11 -
public static void main(string[] args) { HeavyObject objt = HeavyObjectGenerator.getHeavyObject("T"); HeavyObject objh = HeavyObjectGenerator.getHeavyObject("H"); HeavyObject obji = HeavyObjectGenerator.getHeavyObject("I"); HeavyObject objs = HeavyObjectGenerator.getHeavyObject("S"); HeavyObject obji2 = HeavyObjectGenerator.getHeavyObject("I"); HeavyObject objs2 = HeavyObjectGenerator.getHeavyObject("S"); HeavyObject obja = HeavyObjectGenerator.getHeavyObject("A"); HeavyObject objp = HeavyObjectGenerator.getHeavyObject("P"); HeavyObject obje = HeavyObjectGenerator.getHeavyObject("E"); HeavyObject objn = HeavyObjectGenerator.getHeavyObject("N"); System.out.println(objI); System.out.println(objI2); HeavyObjectGenerator というインスタンスを生成するためのクラスが登場しますが それは例えばこんなコードで書かれます package mysample; import java.util.hashmap; import java.util.map; /** * HeavyObject インスタンスを作るクラス */ public class HeavyObjectGenerator { private HeavyObjectGenerator() { private static Map<String, HeavyObject> instancepool = new HashMap<String, HeavyObject>(); public static HeavyObject getheavyobject(string s) { if (!instancepool.containskey(s)) { instancepool.put(s, new HeavyObject(s)); return instancepool.get(s); instancepool というフィールド変数がインスタンスを保持しておくプールになっていて getheavyobject() メソッドにおいてはすでに存在するインスタンスであればそこから引っ張りだされて返却されます もし存在しなければ 新たに new されます main メソッドが実行されると 次のように表示されます 出力例 mysample.heavyobject@921a90 mysample.heavyobject@921a90-12 -
つまり obji と obji2 はまったく同じインスタンスです T I S I T S I H S H 同じものは 1 つずつで十分! 問題 実際にこのクラスを作成しましょう その際 実際に速度の差がわかるように main メソッドは次のように書きましょう public static void main(string[] args) { long start = System.currentTimeMillis(); // ( インスタンス取得 表示処理 ) long end = System.currentTimeMillis(); System.out.println((end - start) / 1000); 5. まとめ 今回は 23 のパターンのうち 3 つにしぼって少し詳しく解説しました たった 3 つですが 少しでもデザインパターンを知ることの価値 意義を感じていただけたら幸いです 次回は 11 月上旬を予定しております 実際の現場の開発ではどういった面に使われているのかを学べたらいいなと考えて鋭意ネタを仕込んでおります 乞うご期待 今日のソースファイル https://docs.google.com/file/d/0b9mqqemgri2wlu96rjloulbysee/edit?usp=sharing - 13 -