ポリモーフィズムの例をもうちっと実用的に書いてみた。
参照元を書き忘れて、トラバが飛んでなかった。元ネタはこちら。http://d.hatena.ne.jp/j5ik2o/20080508/1210246936
Jiemamy作者っぽく、テーブルデータをSQLに変換するプログラムを例に。
モデル
複雑にしないために、わざとpublicフィールド。
import java.util.ArrayList; import java.util.List; public class Table { public String name; public List<Column> columns = new ArrayList<Column>(); public Table(String name) { this.name = name; } }
public class Column { public String name; public String type; public Column(String name, String type) { this.name = name; this.type = type; } }
で、こんな感じで作ったモデルをCREATE TABLE文に直したい。
Table table = new Table("T_HOGE"); table.columns.add(new Column("ID", "integer")); table.columns.add(new Column("CONTENTS", "string"));
こんな風に。
CREATE TABLE T_HOGE( ID INT, CONTENTS TEXT );
ロジック部分(ポリモ未使用版)
public class MySQLConverter { public String convert(Table table) { StringBuilder sb = new StringBuilder(); sb.append("CREATE TABLE ").append(table.name).append("(\n"); for(Column column : table.columns) { sb.append(" ").append(column.name).append(" "); if("integer".equals(column.type)) { sb.append("INT"); } else if("string".equals(column.type)) { sb.append("TEXT"); } else { sb.append(column.type); } sb.append(",\n"); } sb.delete(sb.length()-2, sb.length()-1); sb.append(");\n"); return sb.toString(); } }
public class PostgreSQLConverter { public String convert(Table table) { StringBuilder sb = new StringBuilder(); sb.append("CREATE TABLE ").append(table.name).append("(\n"); for(Column column : table.columns) { sb.append(" ").append(column.name).append(" "); if("integer".equals(column.type)) { sb.append("INTEGER"); } else if("string".equals(column.type)) { sb.append("VARCHAR(32)"); } else { sb.append(column.type); } sb.append(",\n"); } sb.delete(sb.length()-2, sb.length()-1); sb.append(");\n"); return sb.toString(); } }
という個別のコンバータクラスを用意して、こんな感じになるかと。
public class BusinessLogic { public void doBusiness(String database, Table table) { if(database.equals("MySQL")) { MySQLConverter converter = new MySQLConverter(); String sql = converter.convert(table); System.out.println(sql); } else if(database.equals("PostgreSQL")) { PostgreSQLConverter converter = new PostgreSQLConverter(); String sql = converter.convert(table); System.out.println(sql); } else { throw new IllegalArgumentException("そのデータベース知らない: " + database); } } }
public class Main { public static void main(String[] args) { Table table = new Table("T_HOGE"); table.columns.add(new Column("ID", "integer")); table.columns.add(new Column("CONTENTS", "string")); new BusinessLogic().doBusiness("MySQL", table); } }
この doBusinessん中のif〜else ifをなんとかしたい。後でDBが増えるたびにこの列が増えるの、嫌ですよね。
ロジック部分(ポリモ版)
そこでこんなインターフェイスを用意してみる。
public interface Converter { String convert(Table table); }
で、各DB用のコンバータはこいつをimplements
public class PostgreSQLConverter implements Converter { // 同上なので略 }
public class MySQLConverter implements Converter { // 同上なので略 }
そうすると、BusinessLogic部分がこんな風に書ける。
public class BusinessLogic { private Converter converter; public void doBusiness(String database, Table table) { if(database.equals("MySQL")) { converter = new MySQLConverter(); } else if(database.equals("PostgreSQL")) { converter = new PostgreSQLConverter(); } else { throw new IllegalArgumentException("そのデータベース知らない: " + database); } String sql = converter.convert(table); System.out.println(sql); } }
ちょっとすっきりした。けどまだifが残ってるよね。
ロジック部分(ファクトリ導入版)
if文を「ファクトリ」というクラスに追い出してみる。
public class ConverterFactory { public static Converter createConverter(String database) { Converter converter; if(database.equals("MySQL")) { converter = new MySQLConverter(); } else if(database.equals("PostgreSQL")) { converter = new PostgreSQLConverter(); } else { throw new IllegalArgumentException("そのデータベース知らない: " + database); } return converter; } }
public class BusinessLogic { private Converter converter; public BusinessLogic(Converter converter) { this.converter = converter; } public void doBusiness(Table table) { String sql = converter.convert(table); System.out.println(sql); } }
public class Main { public static void main(String[] args) { Converter converter = ConverterFactory.createConverter("MySQL"); Table table = new Table("T_HOGE"); table.columns.add(new Column("ID", "integer")); table.columns.add(new Column("CONTENTS", "string")); new BusinessLogic(converter).doBusiness(table); } }
ほらスッキリした。これで、対応するDatabaseの種類が増えたときは、新しいConverterを書いて、ファクトリだけを修正すれば、メインのロジックに変更が及ばない。これがファクトリ+ポリモのコンボ。
「まぁメリットがあるのは分かった。でも、結局ifをファクトリに移動させただけじゃね?」
という話は、DIのお話へと続きます。。。反響あったら書くw