【Java】Abstract Factoryパターン【デザインパターン】
目次
Abstract Factoryパターンについて
Abstract Factoryパターンとは
- 部品の具体的な実装には注目せず、インターフェースに注目し、そのインターフェースで部品を組み立て製品にまとめるデザインパターンです
- オブジェクト指向における抽象的(Abstract)とは具体的にどのように実装されているか考えずインターフェースのみに注目している状態のことです
FactoryとAbstract Factory の関係について
- 複数のクラスを利用しているアプリケーションで、それらのインスタンスを得る際に、毎回new クラス名()で生成するのではなく、Factoryクラスを作り、そのメソッドを経由してクラスのインスタンスを得るようにします
- さらに、Factoryクラスを抽象クラスにして、クラスのインスタンスを生成するメソッドをFactoryの具象クラスに記述すると、 アプリケーションで利用しているクラスを簡単に入れ替えることができることになります
サンプルプログラム
- Abstract Factoryパターンを使って、階層構造を持ったLink一覧を、HTML形式で出力する例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Factory | 抽象的な工場を表すクラス Link、Tray、Pageを作成 |
Item | LinkとTrayを統一的に扱うクラス |
Link | 抽象的な部品:HTMLリンクを表すクラス |
Tray | 抽象的な部品:LinkやTrayを集めたクラス |
Page | 抽象的な部品:HTMLページを表すクラス |
ListFactory | 具体的な工場を表すクラス ListLink、ListTray、ListPageを作成 |
ListLink | 具体的な部品:HTMLリンクを表すクラス |
ListTray | 具体的な部品:LinkやTrayを集めたクラス |
ListPage | 具体的な部品:HTMLページを表すクラス |
Itemクラス
- LinkとTrayを同一視するためのクラスです
- makeHTMLはサブクラスで実装します
package factory; public abstract class Item { protected String caption; //見出し public Item(String caption) { this.caption = caption; } public abstract String makeHTML(); //HTML文字列が戻る }
Linkクラス
- スーパークラス(Item)のmakeHTMLを実装していないので、Linkクラスも抽象クラスです
package factory; public abstract class Link extends Item { protected String url; public Link(String caption, String url) { super(caption); this.url = url; } }
Trayクラス
- 複数のLinkやTrayを集めてひとまとまりにしたものを表すクラスです
- お盆の上に箇条書き項目を乗せていくイメージです
- Itemクラスの抽象メソッドを継承していますが、実装していないので、Trayクラスも抽象クラスです
package factory; public abstract class Link extends Item { protected String url; public Link(String caption, String url) { super(caption); this.url = url; } }
Pageクラス
- PageクラスはHTMLページ全体を抽象的に表現したクラスです
- 抽象的な製品に当たります
- addメソッドでItemを追加し、追加したものがページで表示されます
package factory; import java.io.*; import java.util.ArrayList; public abstract class Page { protected String title; protected String author; protected ArrayList content = new ArrayList(); public Page(String title, String author) { this.title = title; this.author = author; } public void add(Item item) { content.add(item); } public void output() { //Template Methodパターン try { String filename = title + ".html"; Writer writer = new FileWriter(filename); writer.write(this.makeHTML()); //自分自身のHTMLメソッド writer.close(); System.out.println(filename + " を作成しました。"); } catch (IOException e) { e.printStackTrace(); } } public abstract String makeHTML(); //抽象メソッドを呼ぶ }
Factoryクラス
- getFactoryメソッドで、クラス名を指定して具体的な工場のインスタンスを生成します
- 引数のclassnameに"listfactory.ListFactory"のようにクラス名を文字列で指定します
- forNameメソッドでクラスを動的に読み込み、newInstanceメソッドでそのクラスのインスタンスを作成します
- 戻り値は"抽象的な工場"であることに注意です
- 抽象的な工場で部品や製品を作る抽象メソッドを宣言し、実装はサブクラスに任せます
package factory; public abstract class Factory { public static Factory getFactory(String classname) { Factory factory = null; try { factory = (Factory)Class.forName(classname).newInstance(); } catch (ClassNotFoundException e) { System.err.println("クラス " + classname + " が見つかりません。"); } catch (Exception e) { e.printStackTrace(); } return factory; } public abstract Link createLink(String caption, String url); public abstract Tray createTray(String caption); public abstract Page createPage(String title, String author); }
Mainクラスで動作確認
- 抽象的な工場を使って、抽象的な部品を作って、抽象的な製品を組み立てます
- このクラスでは、具体的な部品、製品、工場を使っていません
- 具体的な工場は、コマンドライン引数で指定します
javac -encoding EUC-JP Main.java listfactory/ListFactory.java
- エンコード形式を指定します
java Main listfactory.ListFactory
- ListFactoryクラスの工場を作るときは、パッケージも含めてパスを指定します
- factoryを使ってLinkを作り、Trayを作って、Trayの中にLink、Trayを乗せて(add)、Pageを作成(output)します
import factory.*; public class Main { public static void main(String[] args) { if (args.length != 1) { System.out.println("Usage: java Main class.name.of.ConcreteFactory"); System.out.println("Example 1: java Main listfactory.ListFactory"); System.out.println("Example 2: java Main tablefactory.TableFactory"); System.exit(0); } Factory factory = Factory.getFactory(args[0]); Link hatena = factory.createLink("はてなブログ", "https://hatenablog.com/"); Link qiita = factory.createLink("Qiita", "https://qiita.com/"); Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/"); Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/"); Link excite = factory.createLink("Excite", "http://www.excite.com/"); Link google = factory.createLink("Google", "http://www.google.com/"); Tray traymatome = factory.createTray("まとめサイト"); traymatome.add(hatena); traymatome.add(qiita); Tray trayyahoo = factory.createTray("Yahoo!"); trayyahoo.add(us_yahoo); trayyahoo.add(jp_yahoo); Tray traysearch = factory.createTray("検索エンジン"); traysearch.add(trayyahoo); traysearch.add(excite); traysearch.add(google); Page page = factory.createPage("お気に入りリスト", "モフネコ"); page.add(traymatome); page.add(traysearch); page.output(); } }
- 実行結果
ListFactoryクラス
- Factoryクラスの抽象メソッド(createLink、createTray、createPage)を実装します
package listfactory; import factory.*; public class ListFactory extends Factory { public Link createLink(String caption, String url) { return new ListLink(caption, url); } public Tray createTray(String caption) { return new ListTray(caption); } public Page createPage(String title, String author) { return new ListPage(title, author); } }
ListLinkクラス
- Linkクラスの抽象メソッドmakeHTMLを実装します
package listfactory; import factory.*; public class ListLink extends Link { public ListLink(String caption, String url) { super(caption, url); } public String makeHTML() { return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n"; } }
ListTrayクラス
- Trayクラスのサブクラスで、makeHTMLを実装します
- trayフィールドにHTMLに出力するItemが入っているので、これらをHTMLで表現します
- 変数itemの中身がListLinkのインスタンスなのか、ListTrayのインスタンスなのかを気にせず、item.makeHTMLすることができます
- つまり、変数itemはItem型で、ItemクラスでmakeHTMLメソッドが宣言されているので、そのサブクラスでは何も考えなくても、makeHTMLを呼び出せます
- あとはitemというインスタンスが、処理の中身を知っているので、いい感じにmakeHTMLメソッドを処理してくれます
package listfactory; import factory.*; import java.util.Iterator; public class ListTray extends Tray { public ListTray(String caption) { super(caption); } public String makeHTML() { StringBuffer buffer = new StringBuffer(); buffer.append("<li>\n"); buffer.append(caption + "\n"); buffer.append("<ul>\n"); Iterator it = tray.iterator(); while (it.hasNext()) { Item item = (Item)it.next(); buffer.append(item.makeHTML()); } buffer.append("</ul>\n"); buffer.append("</li>\n"); return buffer.toString(); } }
ListPageクラス
- makeHMLメソッドでフィールドの内容を使ってページを構成します
package listfactory; import factory.*; import java.util.Iterator; public class ListPage extends Page { public ListPage(String title, String author) { super(title, author); } public String makeHTML() { StringBuffer buffer = new StringBuffer(); buffer.append("<html><head><title>" + title + "</title></head>\n"); buffer.append("<body>\n"); buffer.append("<h1>" + title + "</h1>\n"); buffer.append("<ul>\n"); Iterator it = content.iterator(); //Pageクラスから継承しているフィールド while (it.hasNext()) { Item item = (Item)it.next(); buffer.append(item.makeHTML()); } buffer.append("</ul>\n"); buffer.append("<hr><address>" + author + "</address>"); buffer.append("</body></html>\n"); return buffer.toString(); } }
Abstract Factoryパターンのメリット
- 例えば新たな具体的な工場を追加する場合、Factory、Link、Tray、Pageのサブクラス作り、それぞれの抽象メソッドを実装します
- つまり、factoryパッケージのクラスが持っている抽象的な部分を具体化していくだけで変更が完了します
- それに、いくら具体的な工場を追加・修正しても、抽象的な工場を修正する必要がありません
- 一方、factoryパッケージに部品を新たに作る際には、すでに存在する具体的な工場全てに追加する必要があるので、修正が大変です
- 例えばfactoryクラスにPictureを追加した場合、ListFactoryにcreateImageメソッドを追加し、ListPictureクラスを作成する必要があります
今日のポイント
- 部品の具体的な実装には注目せず、インターフェースに注目し、そのインターフェースで部品を組み立て製品にまとめるデザインパターンです
- Factoryクラスを抽象クラスにして、クラスのインスタンスを生成するメソッドをFactoryの具象クラスに記述すると、 アプリケーションで利用しているクラスを簡単に入れ替えることができることになります
- 具体的な工場を追加したい場合、factoryパッケージのクラスが持っている抽象的な部分を具体化するだけで変更が完了します
- いくら具体的な工場を追加・修正しても、抽象的な工場を修正する必要がありません
- 一方、factoryパッケージに部品を新たに作る際には、すでに存在する具体的な工場全てに追加する必要があるので、修正が大変です
本日もお疲れ様です😊