http://www.nulab.co.jp/designPatterns/designPatterns2/designPatterns2-2.html
こちらのサイトを参考にしました。
このサイトの説明によると、オブジェクトの生成処理と使用処理を同じコードに書くと、生成処理を変更する場合に、使用処理が影響を受けてしまい、再利用性が低下するとのことですが、生成処理をいじると使用処理が影響を受けるというのがしっくりきません。
Java
1Integer a; 2if(value == 1) { 3 a = new Integer(1); 4} else { 5 a = new Integer(2); 6}
上のような生成処理があって、使用処理があったとします。
この生成処理に3のラッパークラスを追加するとして、使用処理に不具合が生じうるのでしょうか?
回答お願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
実際に使っている例を出すのがわかりやすいと思いますので、Javaでよく使うものでもないですが、DOMのdocument.createElement()
という例を考えてみます。
Javaの場合、new
は言語構造と結びついていますので、明記されたクラスのオブジェクトしか返すことができません。一方で、(特にHTMLの場合)createElement()
した時に、返り値に特定のクラスが割り振ってあれば便利です。
このような場合に、メソッドであれば返り値をインターフェース、あるいは抽象クラスにできますので、引数に応じて返る型が変わっても、共通のインターフェースで扱っていい場面なら、そのままハンドリングできます。
Integer
はfinal
なクラスですので、派生型が生じるはずもなく、ファクトリーメソッドの例として考えるのは不適当です。ということで、書き換えてみました。
Java
1// ファクトリーメソッドのないとき 2Number a; 3 4if(value == 1) { 5 a = new Integer("1"); 6} else { 7 a = new Float("2.5"); 8} 9 10// あるとき 11a = SomeNumberFactory((value == 1) ? "1" : "2.5");
投稿2016/09/10 01:09
総合スコア145121
0
ベストアンサー
保守性の観点から考えてみます。
あるクラスAを定義しました。Aを使うために、AをnewしてAのメソッドを呼びます。Aを変更する必要が生じたので、Aの派生クラスBを作りました。Bを使うためには、Aをnewしているコードを、Bをnewするように書き換える必要があります。Aをnewしている箇所が1000箇所あるとしたら修正は嫌ですね。本当に嫌です。
議論のポイント
インターフェイスを利用してポリモーフィズムを機能させれば、動的な機能変更を実現できる。しかし、インターフェイスに代入するオブジェクトを、直接newすることは、柔軟性や、保守性をそこなう。(newをハードコーディングすることは、ソースコードが硬くなり、融通が効かない。)
そのためにファクトリパターンが必要になった。一箇所でインターフェイスを実装するクラスのインスタンスを作れば保守性がよい。クラスが増えてもファクトリの内部を一箇所だけ直せば良い。
ポリモーフィズムは、そこに記述すべきswitch/ifの置き換えであることがある。ポリモーフィズムを活かすにはファクトリメソッドがなければならない。(「リファクタリング」ファウラー)
ファクトリとDI(依存性の注入)は同じ機能ですが、利用者がファクトリを呼ばなくても良いDIのほうが便利です。
時間があれば、サンプルコードを掲載します。
###やり直し(追記)
・使用処理にはポリモーフィズムが働く。ジェネリックな型に派生型のインスタンスを動的に代入できる。ポリモーフィズムは仕様変更に強い。
・生成処理はnewを直接記述するためクラスどうしが強く結合する。利用者が、ポリモーフィズムを働かせるために、サブクラスを使おうとすれば、自分でnewを書き換えなければならない。これは、ポリモーフィズムなど使わず、直接サブクラスを使うことと変わらない。
使用処理と生成処理を同じコードに書くと、ポリモーフィズムを使いたいのに自ら使えなくしていることになる。仕様変更の都度、生成コードを書き換えなければならず、仕様変更に弱い。
###ファクトリ(追記)
ファクトリの目的は、newによるクラスの強い結合をなくしてクラスの独立性を確保することです。そして、凝集度を強めます。凝集度とは密接に関連するクラス間でだけnewを使用してそれを外部に公開しないことです。凝集度とはカプセル化した内部的な結合度のことです。生成処理の弱点を克服します。
外部から自由にnewさせない方法にはどんなものがあるのか。Singletonパターンは、コンストラクタをprivateにして外からインスタンスをnewできなくします。
ファクトリを使う例を示します。二項演算を表す抽象クラスArithmeticOperationを定義します。公開する抽象メソッドは二項演算を行うint calculate(int value1, int value2)ただ一つ。
Java
1/** 2 * 二項演算を定義する。 3 */ 4public abstract class ArithmeticOperation { 5 6 private ArithmeticOperation() {super();} 7 8 /** 9 * 二項演算を実行する。 10 * @param value1 数値1 11 * @param value2 数値2 12 * @return 二項演算の結果を返す。 13 */ 14 public abstract int calculate(int value1, int value2); 15
抽象クラスの実現をインナークラスにします。インナークラスは、加算、減算、乗算、除算の4種類を用意。privateなクラスなので外部からは見えず、インスタンス化もできません。インスタンス化できるのは、抽象クラスArithmeticOperation、またはインナークラス自身です。
Java
1 /** 2 * 加算を定義する。 3 */ 4 private static class Add extends ArithmeticOperation { 5 private Add() {super();} 6 public int calculate(int value1, int value2) { 7 return (value1 + value2); 8 } 9 } 10 11 /** 12 * 減算を定義する。 13 */ 14 private static class Sub extends ArithmeticOperation { 15 private Sub() {super();} 16 public int calculate(int value1, int value2) { 17 return (value1 - value2); 18 } 19 } 20 21 /** 22 * 乗算を定義する。 23 */ 24 private static class Mul extends ArithmeticOperation { 25 private Mul() {super();} 26 public int calculate(int value1, int value2) { 27 return (value1 * value2); 28 } 29 } 30 31 /** 32 * 除算を定義する。 33 */ 34 private static class Div extends ArithmeticOperation { 35 private Div() {super();} 36 public int calculate(int value1, int value2) { 37 return (value1 / value2); 38 } 39 }
最後にstaticなファクトリ機能を用意します。
Java
1 /** 2 * Operator(二項演算)のインスタンスを取得する。 3 * @param operator 二項演算記号 4 * @return Operator(二項演算)のインスタンス 5 */ 6 public static ArithmeticOperation getInstance(String operator) { 7 if (operator == null) { 8 throw new IllegalArgumentException(); 9 } 10 ArithmeticOperation result = null; 11 switch (operator) { 12 case "add": 13 result = new ArithmeticOperation.Add(); 14 break; 15 case "sub": 16 result = new ArithmeticOperation.Sub(); 17 break; 18 case "mul": 19 result = new ArithmeticOperation.Mul(); 20 break; 21 case "div": 22 result = new ArithmeticOperation.Div(); 23 break; 24 default: 25 throw new IllegalArgumentException(); 26 } 27 return result; 28 } 29} 30
ファクトリは文字列を受け取るパラメトリックファクトリです。引数の加減乗除の文字列に応じて二項演算クラスのインスタンスを返します。利用者クラスとの結合度は弱まります。例えば、新しい二項演算が必要なら、新しいクラスを定義してファクトリを変更すれば良いだけ。インスタンス生成のコストが高くつくなら、内部の実装をシングルトンにできます。利用者クラスの変更は必要ありません。
利用者のコードです。
Java
1ArithmeticOperation o = null; 2int result = 0; 3 4o = ArithmeticOperation.getInstance("add"); // ファクトリ 5result = o.calculate(10, 20); // ポリモーフィズム 6System.out.println("10 + 20 = "+ result); 7 8o = ArithmeticOperation.getInstance("sub"); 9result = o.calculate(10, 20); 10System.out.println("10 - 20 = "+ result); 11 12o = ArithmeticOperation.getInstance("mul"); 13result = o.calculate(10, 20); 14System.out.println("10 x 20 = "+ result); 15 16o = ArithmeticOperation.getInstance("div"); 17result = o.calculate(10, 20); 18System.out.println("10 / 20 = "+ result);
ポリモーフィズムとファクトリはセットで使う。フレームワーク担当者はそう考えます。
最後に、あるパッケージを設計する際に、インターフェイスとファクトリだけを外部に公開する。インスタンスの生成はファクトリが隠蔽します。また実装の詳細もパッケージ内に隠蔽します。結合度を弱めることができ、必要なら実装を柔軟に変更できる良い設計ですね。
それから、生成のパターンとしてDI (Dependency Injection)があります。興味があれば調べてみてください。
投稿2016/09/28 23:07
編集2016/09/29 15:34退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2016/10/02 13:39
退会済みユーザー
2016/10/05 01:50
0
単純かつ具体的な例を考えてみました。寓話風ですが細かな点は気にしないでください。
気まぐれなAがBにこんな頼みをしたとします。「整数を表すクラスAIntっての作ったんだけど、"100"とかの文字列を渡せるコンストラクターもあるし、足し算もできるんよ!これ使って2つの文字列(の表す整数)の足し算するメソッド作ってよ。」(ここでJDKにあるjava.lang.Integerなどはないと仮定してください)Bは「簡単すぎる・・・」とつぶやきながらささっとコードを作ります。
java
1public static void add2(String s1, String s2) { 2 AInt i1 = new AInt(s1); 3 AInt i2 = new AInt(s2); 4 AInt result = i1.add(i2); 5 System.out.println(result); 6}
できたプログラムをAへもっていくとAは「ごめん!add2の2つ目はintに入りきらないかもしれない。それでも計算できるようにしてよ。ALongってクラスも作ったからさ!2つ目のパラメータが8文字以上あったらALong使ってね」と言い出します。Bは「はぁ?」と思いつつもうちょっと付き合います。
java
1public static void add2(String s1, String s2) { 2 AInt i1 = new AInt(s1); 3 ALong result; // <-修正 4 if (s2.length() < 8) { // <-修正 5 AInt i2 = new AInt(s2); // <-修正 6 result = i1.add(i2); // <-修正 7 } else { // <-修正 8 AInt i2 = new ALong(s2); // <-修正 9 result = i1.add(i2); // <-修正 10 } // <-修正 11 System.out.println(result); 12}
さて終わったと思ってAへもっていくと、あろうことか「わるいわるい、1つ目の引数もintに入らないケースあったわ!あの・・・作ってくれる?」というではありませんか。Bはさすがに怒りました。「付き合いきれん!最初からこういうふうにしておいてくれよ!」とAに説教を始めます。
java
1// そんなに色々整数クラスつくるんなら共通インターフェースにしといてね 2public interface AInteger { 3 static AInteger valueOf(String s) { 4 return s.length() < 8 ? new AInt(s) : new ALong(s); 5 } 6 AInteger add(AInteger other); 7} 8// intの範囲をサポートする 9public AInt implements AInteger { 10 private int value; 11 public AInt(String s) { ... } 12 public AInteger add(AInteger other) { ... } 13} 14// longの範囲をサポートする 15public ALong implments AInteger { 16 private long value; 17 public ALong(String s) { ... } 18 public AInteger add(AInteger other) { ... } 19}
Bは次のようなコードをささっと書いて、「さあ、これでおしまい!longにも入らない数計算したいならBigIntegerでも使ってABigIntegerとか作ればいいよ。じゃあね!」といいすて去っていきました。
以下がBが最後に残していったソースです。Aがどんなクラスを追加したとしてももう1行も変更するつもりはないようです。Aは「あぁこうしておけばよかったのか・・・」と思いました。
public static void add2(String s1, String s2) { AInteger i1 = AInteger.valueOf(s1); AInteger i2 = AInteger.valueOf(s2); AInteger result = i1.add(i2); System.out.println(result); }
Aさんは個別にクラスを作るのは得意だったのですが、「intの加算を計算できるAInt」ができたときにそれを直接公開せずに将来を見越して「整数の加算が計算できるAIntegerというインターフェース」を設計してそちらを公開しておけばクラスのユーザーであるBさんのコーディングを煩雑にせずに済んだのです。ご質問のページのイントロダクションに書いてあることはこういうことをいっていると思います。
投稿2016/09/28 22:25
編集2016/09/28 22:58総合スコア18392
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2016/10/01 13:55
2016/10/01 15:20
退会済みユーザー
2016/10/05 01:49
0
投稿2016/09/28 17:27
編集2016/09/29 15:08退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2016/09/10 12:49