読者です 読者をやめる 読者になる 読者になる

かとじゅんの技術日誌

技術の話をするところ

今さらデザインパターン Decoratorパターン編

さてさて,ポリモーフィズム,DIという流れで来たのですが,個人的には学習の流れを考えるならポリモーフィズムとDIの間にはデザインパターンを学んだほうがよいと思っています.
というも,DI自体もデザインパターンの一つであるから.まず,日常遭遇する課題に対する定石の答えとしてデザインパターンを把握しておくというのは大事なことだと思います.
今さらデザインパターンなんですが,これが結構奥が深いんですよねー.わかっているといっても人に説明できなかったりして...おさらいの意味も含めて,基本となるGoFぐらいは頭に入れておこうといこうことで,何か書いてみたいと思いますw

is-aとhas-aの関係

Decoratorの話をする前に,ちょっと脱線.
is-aとhas-aの話です.

is-aとは,A is a B ということ,つまりAはB(の一種)であると定義しており,継承を意味します.
例にあげるまでもないですが,Custom is a Baseといえるわけです.

public class Base {

	public void methodA() {
		System.out.println("methodA");
	}

}

public class Custom extends Base {

	public void methodB() {
		System.out.println("methodB");
	}

}

一方,has-aとは,A has a B ということ,つまりAはBを含んでいると定義しており,委譲を意味します.
こちらも,Sample has a Logicなわけですね.

public class Logic {

	public void methodA() {
		System.out.println("methodA");
	}

}

public class Sample {

	private Logic logic = new Logic();

	public void methodA() {
		logic.methodA();
	}

	public void methodB() {
		System.out.println("methodC");
	}

}

Decoratorパターン

さて,話をもとに戻します.

ポリモーフィズムを身につけると,継承(is-a)を利用して差分プログラミングを行い,機能の実装を進めていきます.これはこれで必要なのですが,機能拡張=継承を前提にした場合,実現する機能要件が多様であればあるほど,必然的に継承によりクラス階層が増えてきます.これはゆゆしきことなのです.決して継承は悪ではないのですが,目的が履き違えると一気に可読性,再利用性が低下する恐れがあるってことだと思います.

そこで,機能拡張=何か?ってことですが,ここで出てくる視点が委譲なんです.そうhas-aです.
↓このあたり参考までに.
Wikipedia Decoratorパターン

それでは実際にダイちゃんのところのサンプルでDecoratorを考えてみましょう.
各Converterが返すSQLにDROP TABLEを追加する機能を追加してみたいと思います.
通常なら,MySQLConverter, PostgreSQLConverterの下位にDROP TABLE機能を持ったクラスを持たせるとよいのですが,それではクラスが2つも増えてしまいます.(AbstractConvereterを設けて共通機能として実装したらってのは置いておいてw)

まず,汎用的なConverter用のDecoratorを用意します.これはなくてもよいのですが他にもDecoratorがほしくなったときに便利なので作っておきます.

public abstract class AbstractConverterDecorator implements Converter {

	private Converter converter;

	public AbstractConverterDecorator(Converter converter) {
		this.converter = converter;
	}

	public String convert(Table table) {
		return this.converter.convert(table);
	}
}

で,今回はDROP TABLEを追加するDecoratorを用意します.ロジックは簡単に内部で保持しているDecoratorの返すSQLの前にDROP TABLEを追加するだけです.
まさに,拡張前のもともとの機能は,内部で保持しているconverterに委譲されていることがわかります.

public class DropTableConverterDecorator extends AbstractConverterDecorator {

	public DropTableConverterDecorator(Converter converter) {
		super(converter);
	}

	@Override
	public String convert(Table table) {
		StringBuilder sb = new StringBuilder();
		sb.append("DROP TABLE ").append(table.name).append("\n");
		sb.append(super.convert(table));
		return sb.toString();
	}
}

diconファイルを以下のように修正します.
実行時にはDropTableConverterDecoratorによって拡張されたConveterがDIされるようになります.

 <component name="mysqlBusiness" class="jp.xet.tutorial.s2.BusinessLogic">
  <arg>
   <component class="jp.xet.tutorial.s2.DropTableConverterDecorator">
    <arg>postgreSQLConverter</arg>
   </compoent>
  </arg>
 </component>
 <component name="postgresqlBusiness" class="jp.xet.tutorial.s2.BusinessLogic">
  <arg>
   <component class="jp.xet.tutorial.s2.DropTableConverterDecorator">
    <arg>postgreSQLConverter</arg>
   </compoent>
  </arg>
 </component>

まとめ

このようにすればクラス階層を増やさずに,実行時に機能を追加できるようになります.たとえば, OracleSQLConverterが増えた場合もConverterを実装したクラスであれば同じように DropTableConverterDecoratorを使えば機能を追加できます.このように多様な実装クラスに同じ機能を拡張したい場合は Decoratorパターンが使えます.(今回はDROP TABLEはほぼどのDBMSでも共通表記なので上記のサンプルでも事足りるのですが,それ以外のSQLの場合はなかなかそうはいかないかもしれません.今回はDecoratorを理解するのが目的なのでその点は不問ということでw)
機能拡張=継承でクラス階層を増そうと考える前に,一度委譲によるDecoratorパターンも考えてみるとよいと思います.

補足:Decoratorパターンでもクラスのサフィックス名にDecoratorとつけない場合もあります..*Wrapperとか.クラス名はともかく,重要なのは設計の概念ということだと思います.