【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パッケージに部品を新たに作る際には、すでに存在する具体的な工場全てに追加する必要があるので、修正が大変です
本日もお疲れ様です😊
【Java】Command パターン【デザインパターン】
目次
Commandパターンについて
Commandパターンとは
- Commandパターンは、命令を表すクラスのインスタンスを、1つのものとして表現するパターンです
- 命令の履歴を管理したいときは、そのインスタンスの集まりを管理すればいいことになります
- 命令の集まりを保存しておけば、同じ命令を実行したり、複数の命令をまとめて新しい命令として再利用したりできます
サンプルプログラム
- Commandパターンを使って、簡単なお絵かきプログラム例を取り上げます
- マウスをドラッグすれば赤の点線が描画され、clearを押すと、点が消えます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Command | 命令を表現するインターフェース |
MacroCommand(ConcreteCommand) | 「複数の命令をまとめた命令」を表現するクラス |
DrawCommand(ConcreteCommand) | 「点の描画命令」を表現するクラス |
Drawable | 「描画対象」を表現するインターフェース |
DrawCanvas(Receiver) | 「描画対象」を実装したクラス |
Command インターフェース
- 命令を表現するインターフェースでexecuteメソッドのみを持ちます
- 何が起こるかはMacroCommand・DrawCommandクラスが実装します
package command; public interface Command { public abstract void execute(); }
MacroCommandクラス
- 複数の命令をまとめた命令を表します
- commandsフィールドに複数のCommand実装クラスのインスタンスを集めておきます
- appendメソッドで、Commandを実装したクラスのインスタンスを追加します
- 無限ループにならないよう、ifで自分自身(this)ではないことをチェックします
- undoメソッドで、commandsの最後の命令を削除します
package command; import java.util.Stack; import java.util.Iterator; public class MacroCommand implements Command { // 命令の集合 private Stack commands = new Stack(); // 実行 public void execute() { Iterator it = commands.iterator(); while (it.hasNext()) { ((Command)it.next()).execute(); } } // 追加 public void append(Command cmd) { if (cmd != this) { commands.push(cmd); } } // 最後の命令を削除 public void undo() { if (!commands.empty()) { commands.pop(); } } // 全部削除 public void clear() { commands.clear(); } }
DrawCommandクラス
- 点の描画命令を表現します
- Pointは
java.awt.Point
で定められているクラスで、X,Y座標で二次元平面上の位置を表します - コンストラクタでDrawableインターフェース実装クラスのインスタンスと、Pointクラスのインスタンスを引数に渡して、フィールドに代入します
- "この位置に点を描け"という命令を表しています
package drawer; import command.Command; import java.awt.Point; public class DrawCommand implements Command { // 描画対象 protected Drawable drawable; // 描画位置 private Point position; // コンストラクタ public DrawCommand(Drawable drawable, Point position) { this.drawable = drawable; this.position = position; } // 実行 public void execute() { drawable.draw(position.x, position.y); } }
Drawableインターフェース
- 描画対象を表します
- drawは描画するメソッドです
package drawer; public interface Drawable { public abstract void draw(int x, int y); }
DrawCanvasクラス
- Drawableインターフェース実装クラスで、java.awt.Canvas クラスのサブクラスです
- 自分が描画すべき命令の集合は、MacroCommand型のhistoryフィールドに保持します
- 履歴全体を再描画する時は、paintメソッドで
history.execute()
を呼ぶだけで、保持されている命令の集まりが再実行されます
package drawer; import command.*; import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class DrawCanvas extends Canvas implements Drawable { // 描画色 private Color color = Color.red; // 描画する点の半径 private int radius = 6; // 履歴 private MacroCommand history; // コンストラクタ public DrawCanvas(int width, int height, MacroCommand history) { setSize(width, height); setBackground(Color.white); this.history = history; } // 履歴全体を再描画 public void paint(Graphics g) { history.execute(); } // 描画 public void draw(int x, int y) { Graphics g = getGraphics(); g.setColor(color); g.fillOval(x - radius, y - radius, radius * 2, radius * 2); } }
Mainクラスで動作確認
- hostoryで描画履歴を保持し、DrawCanvas に渡します
- 描画履歴はMainとDrawCanvasで共有されているということになります
import command.*; import drawer.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Main extends JFrame implements ActionListener, MouseMotionListener, WindowListener { // 描画履歴 private MacroCommand history = new MacroCommand(); // 描画領域 private DrawCanvas canvas = new DrawCanvas(400, 400, history); // 消去ボタン private JButton clearButton = new JButton("clear"); // コンストラクタ public Main(String title) { super(title); this.addWindowListener(this); canvas.addMouseMotionListener(this); clearButton.addActionListener(this); Box buttonBox = new Box(BoxLayout.X_AXIS); //横にボタンを並べるボックス buttonBox.add(clearButton); Box mainBox = new Box(BoxLayout.Y_AXIS); //縦にボタンを並べるボックス mainBox.add(buttonBox); mainBox.add(canvas); getContentPane().add(mainBox); ..コンテナにコンポーネントを載せる pack(); show(); } // ActionListener用 public void actionPerformed(ActionEvent e) { if (e.getSource() == clearButton) { history.clear(); canvas.repaint(); } } // MouseMotionListener用 public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent e) { Command cmd = new DrawCommand(canvas, e.getPoint()); history.append(cmd); //実行履歴に追加 cmd.execute(); //即実行 } // WindowListener用 public void windowClosing(WindowEvent e) { System.exit(0); } public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} public static void main(String[] args) { new Main("Command Pattern Sample"); } }
Commandパターンのメリット
- 命令をオブジェクトとして表現することで、命令の履歴をとったり、命令の再実行を行うことができます
- 新しい命令を追加したい場合は、Commandインターフェース実装クラスを作成すればよく、機能拡張が行いやすくなります
- ConcreteCommand自身が、Receiver(描画対象)も知っていることで、ConcreteCommandを誰が管理してもいつでもexecuteできるようになります
アダプターについて
- プログラミングを簡略化するために、アダプターというクラス群が
java.awt.event
パッケージに用意されています(Adapterパターンの一例)- MouseMotionListenerインターフェースに対して、MouseMotionAdapterクラスなど
- MouseMotionAdapterクラスでは、MouseMotionListenerインターフェースを実装し、要求するメソッドを全部提供します *しかし、提供されているインターフェースの実装の中身は空になっているので、MouseMotionAdapterクラスのサブクラスを作り、必要なメソッドを実装する事で、目的を達成できます
- 匿名インナークラス機構を組み合わせてアダプターを使うと、スマートに記述できます
- MouseMotionAdapterクラスの名前なしサブクラスを作り、そのインスタンスを生成できます
- オーバーライドしたいメソッドのみ実装すれば、他は書く必要がありません
今日のポイント
- Commandパターンは、命令を表すクラスのインスタンスを、1つのものとして表現するパターンです
- 命令をオブジェクトとして表現することで、命令の履歴をとったり、命令の再実行を行うことができます
- 新しい命令を追加したい場合は、Commandインターフェース実装クラスを作成すればよく、機能拡張が行いやすくなります
本日もお疲れ様です😊
【Java】Builderパターン【デザインパターン】
目次
Builderパターンについて
Builderパターンとは
- 全体を構成している、各部分部分の処理を積上げていくためのパターンです
サンプルプログラム
- Builderパターンを使って、タイトル、文字列、箇条項目を含む文章を作成する例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Builder | 文章を構成するメソッドを定めた抽象クラス 表現形式を決定する |
Director | 一つの文章を作るクラス 作成過程を決定する |
TextBuilder | プレーンテキストで文章を作るクラス |
HTMLBuilder | HTMLファイルでで文章を作るクラス |
Builderクラス
- 文章を作るメソッドを宣言する抽象クラスです
public abstract class Builder { public abstract void makeTitle(String title); public abstract void makeString(String str); public abstract void makeItems(String[] items); public abstract void close(); }
Directorクラス
- Builderクラスで宣言されているメソッドで文章を作ります
- Builderクラスは抽象クラスなので、インスタンスを生成できません
- Directorクラスのコンストラクタに渡されるのは、Builderクラスのサブクラスのインスタンスです
- 与えられたインスタンスの種類によって、Direntorで作るインスタンスの形式が決まります
public class Director { private Builder builder; public Director(Builder builder) { // 与えられたBuilderのサブクラスのインスタンスを、builderフィールドに保持 this.builder = builder; } public void construct() { // 文書構築メソッド builder.makeTitle("Greeting"); // タイトル builder.makeString("朝から昼にかけて"); // 文字列 builder.makeItems(new String[]{ // 箇条書き "おはようございます。", "こんにちは。", }); builder.makeString("夜に"); // 別の文字列 builder.makeItems(new String[]{ // 別の箇条書き "こんばんは。", "おやすみなさい。", "さようなら。", }); builder.close(); // 文書を完成させる } }
TextBuilderクラス
- Builderクラスのサブクラスです
public class TextBuilder extends Builder { private StringBuffer buffer = new StringBuffer(); public void makeTitle(String title) { buffer.append("==============================\n"); buffer.append("『" + title + "』\n"); buffer.append("\n"); } public void makeString(String str) { buffer.append('■' + str + "\n"); buffer.append("\n"); } public void makeItems(String[] items) { for (int i = 0; i < items.length; i++) { buffer.append(" ・" + items[i] + "\n"); } buffer.append("\n"); } public void close() { buffer.append("==============================\n"); } public String getResult() { // 完成した文書 return buffer.toString(); // StringBufferをStringに変換 } }
HTMLBuilderクラス
- Builderクラスのサブクラスです
- 文章を構築し、HTMLファイル名を返します
import java.io.*; public class HTMLBuilder extends Builder { private String filename; // 作成するファイル名 private PrintWriter writer; // ファイルに書き込むPrintWriter public void makeTitle(String title) { filename = title + ".html"; try { writer = new PrintWriter(new FileWriter(filename)); } catch (IOException e) { e.printStackTrace(); } writer.println("<html><head><title>" + title + "</title></head><body>"); writer.println("<h1>" + title + "</h1>"); } public void makeString(String str) { writer.println("<p>" + str + "</p>"); } public void makeItems(String[] items) { writer.println("<ul>"); for (int i = 0; i < items.length; i++) { writer.println("<li>" + items[i] + "</li>"); } writer.println("</ul>"); } public void close() { writer.println("</body></html>"); writer.close(); } public String getResult() { // 完成した文書のファイル名を返す return filename; } }
Mainクラスで動作確認
public class Main { public static void main(String[] args) { if (args.length != 1) { usage(); System.exit(0); } if (args[0].equals("plain")) { TextBuilder textbuilder = new TextBuilder(); Director director = new Director(textbuilder); director.construct(); String result = textbuilder.getResult(); System.out.println(result); } else if (args[0].equals("html")) { HTMLBuilder htmlbuilder = new HTMLBuilder(); Director director = new Director(htmlbuilder); director.construct(); String filename = htmlbuilder.getResult(); System.out.println(filename + "が作成されました。"); } else { usage(); System.exit(0); } } public static void usage() { System.out.println("Usage: java Main plain プレーンテキストで文書作成"); System.out.println("Usage: java Main html HTMLファイルで文書作成"); } }
Builderパターンのメリット
- MainはDirectorクラスのconstructメソッドのみを呼び出し、
- DirectorクラスはConcreteBuilderクラスに依存せず、Builderクラスのメソッドのみを利用します
- →ConcreteBuilderクラスは必要に応じて変更することができます
- Builderの抽象メソッドにどのような動作が期待されているのかを知っておくのが重要です
今日のポイント
- 全体を構成している、各部分部分の処理を積上げていくためのパターンです
- DirectorクラスはConcreteBuilderクラスに依存しないので、必要に応じて変更することができます
- 例えば、同じ文書を要求に合わせて、異なる表現形式で出力することができます
本日もお疲れ様です😊
【Java】Prototypeパターン【デザインパターン】
目次
Prototypeパターンについて
Prototypeパターンとは
サンプルプログラム
- Prototypeパターンを使って、文字列を枠線で囲んだり、下線を引いて表示する例を取り上げます
- 各クラスやインターフェースの役割は以下のようになっています
名前 | 役割 |
---|---|
Product (Prototype) | 抽象メソッドuse,createCloneを宣言するインターフェース |
Manager (Client) | createCloneを使ってインスタンスを複製するクラス |
ManagerBox (ConcretePrototype) | 文字列を枠線で囲って表示するクラス use,createCloneを実装 |
UnderlinePen (ConcretePrototype) | 文字列に下線を引いて表示するクラス use,createCloneを実装 |
Productインターフェース
java.lang.Cloneable
インターフェースを継承し、cloneメソッドで自動的に複製を行います- Cloneableインターフェースはマーカーインターフェースです
package framework; import java.lang.Cloneable; public interface Product extends Cloneable { public abstract void use(String s); public abstract Product createClone(); }
Managerクラス
- インスタンスの複製を行います
- showcaseフィールドに、インスタンスの名前とインスタンスの対応関係をHashMapで保存します
- registerメソッドでは、製品の名前とProductインターフェースを与えられたら、showcaseに登録します
- 引数のProductインターフェース(proto)は、Productインターフェースを実装したクラスのインスタンスで、use,createCloneメソッドを呼ぶことができます
- ProductインターフェースやManagerクラスにはManagerBoxやUnderlinePenクラス名は使用しません
- つまり、ProductインターフェースやManagerクラスは、独立に修正することができます(疎結合)
- Productインターフェースが、Managerクラスと他のクラスを紐づけています
package framework; import java.util.*; public class Manager { private HashMap showcase = new HashMap(); public void register(String name, Product proto) { showcase.put(name, proto); } public Product create(String protoname) { Product p = (Product)showcase.get(protoname); return p.createClone(); } }
MessageBoxクラス
- Productインターフェースを実装します
- createCloneメソッドで呼び出しているcloneメソッドは自分自身の複製を行うメソッドで、インスタンスが持つフィールド値も新しいインスタンスにコピーします
- cloneメソッドは浅いコピー(shallow copy)で、参照のみコピーします
- cloneメソッドでコピーできるのは、
java.lang.Cloneable;
インターフェース実装クラスのみです - インターフェースが実装されていないと、CloneNotSupportedExceptionが投げられるので、try...catch構文で例外処理を実装します
- MessageBoxクラスで実装しているProductインターフェースで、Cloneableインターフェースを実装しているので、例外は投げられません
- cloneメソッドは自分とそのサブクラスからしか呼べないので、別のメソッド(createClone)で包む必要があります
import framework.*; public class MessageBox implements Product { private char decochar; //囲み文字(*など) public MessageBox(char decochar) { this.decochar = decochar; } public void use(String s) { int length = s.getBytes().length; for (int i = 0; i < length + 4; i++) { System.out.print(decochar); } System.out.println(""); System.out.println(decochar + " " + s + " " + decochar); for (int i = 0; i < length + 4; i++) { System.out.print(decochar); } System.out.println(""); } public Product createClone() { Product p = null; try { p = (Product)clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } }
UnderlinePenクラス
- Productインターフェースを実装します
- 与えられた文字列を引用符で括り、ulcharで与えられた文字で下線を引きます
import framework.*; public class UnderlinePen implements Product { private char ulchar; public UnderlinePen(char ulchar) { this.ulchar = ulchar; } public void use(String s) { int length = s.getBytes().length; System.out.println("\"" + s + "\""); System.out.print(" "); for (int i = 0; i < length; i++) { System.out.print(ulchar); } System.out.println(""); } public Product createClone() { Product p = null; try { p = (Product)clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } }
Mainクラスで動作確認
import framework.*; public class Main { public static void main(String[] args) { // 準備 Manager manager = new Manager(); UnderlinePen upen = new UnderlinePen('~'); MessageBox mbox = new MessageBox('*'); MessageBox sbox = new MessageBox('/'); manager.register("strong message", upen); manager.register("warning box", mbox); manager.register("slash box", sbox); // 生成 Product p1 = manager.create("strong message"); p1.use("Hello, world."); Product p2 = manager.create("warning box"); p2.use("Hello, world."); Product p3 = manager.create("slash box"); p3.use("Hello, world."); } }
Prototypeパターンのメリット
- ManagerクラスはcreateCloneを呼びますが、どのクラスのインスタンスを複製するかは知らなくとも、Productインターフェースを実装するクラスであれば、そのインスタンスを複製できます
- Productインターフェースを実装するクラスのインスタンスを作成し、Managerに登録しておけば好きな時に複製できます
- →こうすることで雛形をいくらでも増やすことができます(
~
で文字列に下線を引いたり、*
や/
で文字列に枠をつける等)- それぞれ別のクラスにしていたら、クラスの数が多くなり管理が大変になります。。
- インスタンスの複製(clone)をframeworkパッケージに閉じ込めています
- →インスタンスを生成するフレームワークを、特定のインスタンスに依存しない様に作ることができます
今日のポイント
- Prototypeパターンは、クラスからインスタンスを生成するのではなく、模型となるインスタンスから別のインスタンスを複製(clone)して生成するパターンです
- PrototypeのインターフェースやClientクラスにはConcretePrototypeクラス名は使用しません(=疎結合である)
- つまり、PrototypeのインターフェースやClientクラスは、切り離して部品として再利用、修正することができます
- インスタンスの複製(clone)をframeworkパッケージに閉じ込めることで、インスタンスを生成するフレームワークを、特定のインスタンスに依存しない様に作ることができます
本日もお疲れ様です😊
【Java】Singletonパターン【デザインパターン】
目次
Singletonパターンについて
Singletonパターンとは
サンプルプログラム
名前 | 役割 |
---|---|
Singleton | インスタンスが1つしかないクラス |
Main | 動作テスト用クラス |
Singletonクラス
- staticフィールド(クラス変数)としてsingletonを定義し、Singletonクラスのインスタンスで初期化します
- コンストラクタをprivateにすることで、Singletonクラスの外でコンストラクタの呼び出しを禁止します
- これで、インスタンスが1つしか生成されないことを保証することができます
- 唯一のインスタンスを得るためのstasticメソッド(getInstance)で同じインスタンスを返します
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { System.out.println("インスタンスを生成しました。"); } public static Singleton getInstance() { return singleton; } }
Mainクラスで動作確認
- SingletonクラスのgetInstanceメソッドを使ってSingletonのインスタンスを得ています
- ここで、obj1,obj2のように二回インスタンスを得ていますが、同一性の判定結果は、"obj1とobj2は同じインスタンスです" と表示されます
- Singletonクラスのコンストラクタは一回しか呼ばれていないので、getInstanceしても同じインスタンスが帰されることになります!
public class Main { public static void main(String[] args) { System.out.println("Start."); Singleton obj1 = Singleton.getInstance(); Singleton obj2 = Singleton.getInstance(); if (obj1 == obj2) { System.out.println("obj1とobj2は同じインスタンスです。"); } else { System.out.println("obj1とobj2は同じインスタンスではありません。"); } System.out.println("End."); } }
Singletonパターンのメリット
今日のポイント
- 生成するインスタンスの数を1つに制限するデザインパターンです
- Singletonクラスのコンストラクタをprivateにすることで、Singletonクラスの外でコンストラクタの呼び出しを禁止します
- これによって、インスタンスが1つしか生成されないことを保証することができます
- Singletonクラス内の、唯一のインスタンスを得るためのstasticメソッド(getInstance)で同じインスタンスを返します
- Singleton利用クラスから、Singletonクラスのインスタンスを複数回得る(getInstance)場合でも、 Singletonクラスのコンストラクタは一回しか呼ばれないので、同一のインスタンスが帰されることになります
本日もお疲れ様です😊
【Java】Factory Methodパターン【デザインパターン】
目次
Factory Methodパターンについて
Factory Methodパターンとは
- インスタンスの作り方をスーパークラスで定め、サブクラスで具体的に記述するパターンです
- インスタンスを生成する工場(factory)をTemplate Mathodパターンで構成する、という事です
- (参考)Template Mathodパターン
- インスタンス生成の枠組みと、実際のインスタンス生成のクラスとを分けて考えることができるようになります
サンプルプログラム
- Factory Methodパターンを使って、身分証明書カードを作成する例を取り上げます
- 各クラスの役割は以下のようになっています
- frameworkパッケージは、インスタンス生成の枠組みの役割を担います
- idcardパッケージは、インスタンスの具体的な実装を行います
名前 | 役割 |
---|---|
Product (frameworkパッケージ) | useメソッドのみ定義されている抽象クラス |
Factory (frameworkパッケージ) | createメソッドを実装している抽象クラス |
IDCard (idcardパッケージ) | useメソッド実装クラス |
IDCardFactory (idcardパッケージ) | createProduct、registerProductメソッド実装クラス |
Productクラス
- 製品を表現しています
- ここでの製品とは、"何であれuseできるもの"と、定めています
- useメソッドの宣言のみ行っていて、具体的な実装は全てサブクラスに任せます
package framework; public abstract class Product { public abstract void use(); }
Factoryクラス
- Template Method パターンが使われています
- createメソッドがテンプレートメソッドです
- 抽象メソッドcreateProduct、registerProduct(製品を作る、登録する)はサブクラスで実装します
- Factoryクラスは、"createメソッドでProductインスタンスを生成し、createProductして、registerProductする"、という手順で実装されています
package framework; public abstract class Factory { public final Product create(String owner) { Product p = createProduct(owner); registerProduct(p); return p; } protected abstract Product createProduct(String owner); //製品を作る protected abstract void registerProduct(Product product); //製品を登録する }
IDCardクラス
- 認証番号をカードを表します
- Productクラスで宣言されたuseメソッドを実装します
package idcard; import framework.*; public class IDCard extends Product { private String owner; IDCard(String owner) { System.out.println(owner + "のカードを作ります。"); this.owner = owner; } public void use() { System.out.println(owner + "のカードを使います。"); } public String getOwner() { return owner; } }
IDCardFactoryクラス
- Factoryクラスで宣言されたcreateProduct、registerProductメソッドを実装します
- createProductでIDcardのインスタンスを生成します
- registerProductでIDcardのownerをownersフィールドに追加しています
package idcard; import framework.*; import java.util.*; public class IDCardFactory extends Factory { private List owners = new ArrayList(); protected Product createProduct(String owner) { return new IDCard(owner); } protected void registerProduct(Product product) { owners.add(((IDCard)product).getOwner()); } public List getOwners() { return owners; } }
Mainクラスで動作確認
- createメソッドでIDcardを作って、useメソッドで使います
import framework.*; import idcard.*; public class Main { public static void main(String[] args) { Factory factory = new IDCardFactory(); Product card1 = factory.create("ナウシカ"); Product card2 = factory.create("キキ"); Product card3 = factory.create("千尋"); card1.use(); card2.use(); card3.use(); } }
Factory Methodパターンのメリット
- Creater(Factoryクラス)は、Product(Productクラス)と、インスタンス生成のメソッド(createProduct、registerProductメソッド)を知っていればProduct(IDcard)を生成することができます
- newによるインスタンス生成を、 インスタンス生成のためのメソッド呼び出しに変えることで、具体的なクラス名(IDCardFactoryクラス)に束縛されません
- frameworkパッケージは、idcardパッケージに依存していません
- →他の製品を作りたい場合、
import framework.*;
した別のパッケージを作れば良いだけで、frameworkパッケージの中身を変更する必要がありません
今日のポイント
- インスタンスの作り方をスーパークラスで定め、サブクラスで具体的に記述するパターンです
- インスタンス生成の枠組みと、実際のインスタンス生成のクラスとを分けて考えることができるようになります
- インスタンス生成の枠組みを記述する(framework)パッケージは、インスタンスの具体的な実装を行うパッケージに依存していません
- →他のProductを生成したい場合、
import framework.*;
した別のパッケージを作れば良いだけで、frameworkパッケージの中身を変更する必要がありません
本日もお疲れ様です😊
【Java】Template Methodパターン【デザインパターン】
目次
- Template Methodパターンについて
- Template Methodパターンとは
- Template Methodパターンのメリット
- LSP (The Liskov Substitution Principle) とは
- 今日のポイント
Template Methodパターンについて
Template Methodパターンとは
- スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるデザインパターンです
- スーパークラスでは、テンプレートとなる抽象メソッドを定義します
- テンプレートとは、
文書などのコンピュータデータを作成する上で雛形となるデータ
- テンプレートとは、
- のことで、例えば、カリグラフィーなど文字を綺麗に書くためのテンプレート板などがあります
- テンプレート板を使えば、どんなペンでなぞっても同じデザインの文字を描くことができます
- オブジェクト指向言語では、同じような処理を1つのクラスとして纏めるように設計するので、Template Methodパターンに則っているということですね〜💡
サンプルプログラム
- Template Methodパターンを使って、文字や文字列を5回繰り返して表示する例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
AbstractDisplay (AbstractClass) | displayメソッドのみ実装されている抽象クラス |
CharDisplay (ConcreteClass) | open、print、closeを実装しているクラス 文字表示をする |
StringDisplay (ConcreteClass) | open、print、closeを実装しているクラス 文字列表示をする |
AbstractDisplayクラス
- 抽象クラスAbstractDisplayでは、 サブクラスに実装をまかせる抽象メソッド (open,print,close) を用意します
- AbstractDisplayクラスでは、displayメソッドを実装します
- まずopenして、5回printを繰り返して、最後にcloseします
- displayメソッドがテンプレートメソッドになります
public abstract class AbstractDisplay { public abstract void open(); public abstract void print(); public abstract void close(); public final void display() { open(); for (int i = 0; i < 5; i++) { print(); } close(); } }
CharDisplayクラス
- CharDisplayクラスは、AbstractDisplayのサブクラスです
- コンストラクタで渡された文字chを、フィールドに保存します
- スーパークラスの抽象メソッド (open,print,close) を、オーバーライドして実装します
- printメソッドでは、フィールドに記憶しておいた文字を1個表示します
- AbstractDisplayクラスのdisplayメソッドから繰り返して呼び出されます
- スーパークラスで宣言したメソッドをサブクラスで実装する際には、実装したメソッドが呼ばれるタイミングを理解しておきましょう
public class CharDisplay extends AbstractDisplay { private char ch; public CharDisplay(char ch) { this.ch = ch; } public void open() { System.out.print("<<"); } public void print() { System.out.print(ch); } public void close() { System.out.println(">>"); } }
StringDisplayクラス
- StringDisplayクラスは、AbstractDisplayのサブクラスです
- コンストラクタで渡された文字列stringと、バイト単位の幅widthを、フィールドに記憶します
- スーパークラスの抽象メソッド (open,print,close) を、オーバーライドして実装します
- open、closeメソッドでは、StringDisplayクラスのメソッドprintLineで線を引きます
- printメソッドでは、フィールドに記憶しておいた文字列の前後に"|"をつけて表示します
- printLineメソッドはprivateなので、このクラスの中だけで使われます
public class StringDisplay extends AbstractDisplay { private String string; // 表示するべき文字列 private int width; // バイト単位で計算した文字列の幅 public StringDisplay(String string) { this.string = string; this.width = string.getBytes().length; } public void open() { printLine(); } public void print() { System.out.println("|" + string + "|"); } public void close() { printLine(); } private void printLine() { System.out.print("+"); for (int i = 0; i < width; i++) { System.out.print("-"); } System.out.println("+"); } }
Mainクラスで動作確認
- CharDisplay、StringDisplayのインスタンスを1個作ります
- 作成したインスタンス(d1,d2,d3)は、AbstractDisplayのサブクラスのインスタンスなので、 継承したdisplayメソッドを呼び出すことができます
- 実際の動作は個々のクラスCharDisplayやStringDisplayで定まります
public class Main { public static void main(String[] args) { AbstractDisplay d1 = new CharDisplay('H'); AbstractDisplay d2 = new StringDisplay("Hello, world."); AbstractDisplay d3 = new StringDisplay("こんにちは。"); d1.display(); d2.display(); d3.display(); } }
Template Methodパターンのメリット
- スーパークラスのテンプレートメソッドでアルゴリズムが記述されているので、サブクラスではアルゴリズムを記述しなくて良いです
- アルゴリズムがが各クラスに散らばっていないので、テンプレートメソッドにバグがあった場合に改修がしやすいです
- スーパークラス型の変数に、サブクラスのインスタンスが代入されているので、サブクラスの種類を特定しなくても動くコードになります
LSP (The Liskov Substitution Principle) とは
- クラス同士の継承関係が正しいか、を検証する原則で、リスコフの置換原則ともいわれます
- Template Methodパターンに限らない、継承の一般的な原則です
- 具体的には、スーパークラスとサブクラスを定義するとき、サブクラス別のサブクラスに置き換えても、スーパークラスがは正常に動作しないといけないというルールです
- 継承関係でバグが発生しないように実装するときに気をつけるべきは、以下の点です
今日のポイント
- Template Methodパターンは、スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるデザインパターンです
- 同じような流れの処理を共通化したい時に、Template Methodパターンを適応することができます
- スーパークラス(抽象クラス)では、 サブクラスに実装をまかせる抽象メソッド を用意します
- スーパークラス型の変数に 、サブクラスのインスタンスを代入することで、サブクラスの種類を特定しなくても動くコードになります
- よって、リスコフの置換原則に則った設計ができているということになります
本日もお疲れ様です😊