dorivenの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】コンパウンドパターン【事前学習編1】

どうも、昨日見事にブログを書き忘れていました。

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!

Compoundパターンについて



今回は11章では、【Compound】パターンというデザインパターンについて話して行きたいのですが、
ネットでCompoundを検索しても、一切引っかかりません。
実はCompoundパターンというデザインパターンは存在しません。
ですから、もちろんGoF本にも掲載されていないです。

では、Compoundパターンとは一体何を指すものなのでしょうか?
それはデザインパターンを複雑に組み合わせた一つのアーキテクチャです。
皆さんがフレームワークなどで触っているMVCもその一つです。

更にWeb上で触れられているMVCとクライアントでのMVCにも若干の違いがあります。
この違いを理解することで、Web上のMVCの理解をする助けにもなるでしょう。

事前学習



参考書では今までに触れたデザインパターンについて、鴨のシュミレータを通して復習しています。

Quackableインターフェース

1章では鴨の鳴き声が違う事を解決する為に、Quackableインターフェースを作成していました。
ガーガー鳴く鴨も居れば、鴨のおもちゃでキューキュー音を出す物もあります。
このQuackableインターフェースは、鴨の鳴き声を実装する、ためのインターフェースです。
下記の通り、実装する必要のある関数も鳴き声だけになっています。

public interface Quackable{
  public void quack(); // 鴨の鳴き声実装
}

このように鴨によって異なる鳴き声を実装していきます。

public class DuckCall implements Quackable{
  public void quack(){
    System.out.println("ガアガア");
  }
}

public void RubberDuck implements Quackable{
  public void quack(){
    System.out.println("キューキュー");
  }
}
Adapterパターン

シミューレータでは鴨だけでなく、どうやらガチョウも登場するようです。
ガチョウは以下のような振る舞いをします。

public class Goose{
  public void honk(){
    System.out.println("ガー");
  }
}

しかし、このままではこのガチョウを使用することは出来ません。
何故なら、振る舞いが違うからです(quack()とhonk()で関数名が異なる)
なので、前に触れたAdapterパターンを使用してこの問題を解消したいと思います。
以下がガチョウとをAdapterパターンで連結したものです。

public class GooseAdapter implements Quackable{
  Goose goose;
  
  public GooseAdapter(Goose goose){
    this.goose = goose;
  }

  public void quack(){
    goose.honk();
  }
}

さて、これで以下のようにインスタンス化することでガチョウを鴨として動作させることが出来るようになりました。

Quakable gooseDuck = new GooseAdapter(new Goose());
Docoratorパターン

どうやら、ここで以下の様な要望が入ったようです。
「鴨が鳴いた回数を知りたいんだよね。」

さて、これを以前のソースコードを弄らずに触るにはどうしたらいいのか?
ここでDecoratorパターンを使って、Quackableを実装したQuackCounterを作ってみましょう。
Decoratorは自身のinterfaceクラスをコンストラクタの引数に持つことで、
そのクラス自身の処理を呼び出しながら付加的な機能を付けられるデザインパターンです。

public class QuackCounter implements Quackable{
  Quackable duck;
  static int numberOfQuacks;

  public QuackCounter(Quackable duck){
    this.duck = duck;
  }

  public void quack(){
    duck.quack();
    numberOfQuacks++; // quack()が呼ばれる度に鴨が鳴いた関数がカウントされる
  }

  /**
   * 鴨が鳴いた回数を返す
   */
  public static int getQuacks(){
    return numberOfQuacks;
  }
}

後は以下のようにインスタンス化時にDecoratorを通します。

Quackable mallardDuck = new QuackCounter(new MallardDuck());

これでquqckが呼び出される度にカウントが行われるようになりました。

AbstractFactoryパターン

しかし、これではわざわざQuackCounterを通してインスタンス化する必要があり、
QuackCounterのコンストラクタの処理が変更された場合は、newの部分を全て変更しなければなりません。
この処理をひとつに集中させることは出来ないでしょうか?

そこでAbstractFactoryパターンを使ってみましょう。
Factoryパターンはインスタンスの生成を集中して管理するデザインパターンです。
まずはベースになるAbstractDuckFactoryの仮想クラスを作ります。

public static class AbstractDuckFactory{
  public abstract Quackable createMallardDuck();
  public abstract Quackable reateRedheadDuck();
  public abstract Quackable createDuckCall();
  public abstract Quackable createRubberDuck();
}

これをカウンティングを行うクラスの生成と、行わないクラスの生成でFactoryを分けます。

public class DuckFactory extends AbstractDuckFactory{
  public Quackable createMallardDuck(){
    return new MallardDuck();
  }

  public Quackable createRedheadDuck(){
    return new RedheadDuck();
  }

  public Quackable createDuckCall(){
    return new DuckCall();
  }

  public Quackable createRubberDuck(){
    return new RubberDuck();
  }
}

public class CountingDuckFactory extends AbstractDuckFactory{
  public Quackable createMallardDuck(){
    return new QuackCounter(MallardDuck());
  }

  public Quackable createRedheadDuck(){
    return new QuackCounter(RedheadDuck());
  }

  public Quackable createDuckCall(){
    return new QuackCounter(DuckCall());
  }

  public Quackable createRubberDuck(){
    return new QuackCounter(RubberDuck());
  }
}

この様に2つのFactoryに分けて処理を継承することで、
数え上げをしたい場合としない場合を集中して扱うことが出来るようになりました。

後は宣言したFactoryを通して以下のようにインスタンスを生成するだけです。

AbstractDuckFactory duckFactory = new CountingDuckFactory();
Quackable mallardDuck = duckFactory.craeteMallardDuck();
QuackCounter.getQuacks(); // 鳴いた回数

これでインスタンスの生成を一元的に管理出来るようになりました。

さて、かなり記事が長くなってきたので一旦ここで終了しますね。

次回予告



さて、今まで習ってきたパターンが沢山出てきましたね。
実際の例を取り上げて、このようにデザインパターンを組み合わせてやることで、
ソースコードの依存性が下がり、凝集度をあげられているのが分かると思います。

次回は更にパターンの組み合わせを行い、この鴨市ミューレータがどのようなクラス図になっているかを例示します。

まずは今までのデザインパターンを理解し、実際にMVCがどのようなデザインパターンの組み合わせてで動いているのか?
そして、Webに切り替えることで、そのパターンがどのように変更されてるのか、という順番で話を展開させていきたいと思います。

それでは、また!