さてさて,ポリモーフィズム,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とか.クラス名はともかく,重要なのは設計の概念ということだと思います.