質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

4回答

1404閲覧

以下の状況での再利用性

退会済みユーザー

退会済みユーザー

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2016/09/10 00:36

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

実際に使っている例を出すのがわかりやすいと思いますので、Javaでよく使うものでもないですが、DOMのdocument.createElement()という例を考えてみます。

Javaの場合、newは言語構造と結びついていますので、明記されたクラスのオブジェクトしか返すことができません。一方で、(特にHTMLの場合)createElement()した時に、返り値に特定のクラスが割り振ってあれば便利です。

このような場合に、メソッドであれば返り値をインターフェース、あるいは抽象クラスにできますので、引数に応じて返る型が変わっても、共通のインターフェースで扱っていい場面なら、そのままハンドリングできます。

Integerfinalなクラスですので、派生型が生じるはずもなく、ファクトリーメソッドの例として考えるのは不適当です。ということで、書き換えてみました。

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

maisumakun

総合スコア145121

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/09/10 12:49

すいません、creatElement()はコード上にないみたいですが、これに言及することで何をお伝えしたかったのでしょうか?
guest

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/01 14:27

質問が4点あります。 お答え頂ければ嬉しいです。 1:「ジェネリックな型」とはスーパークラスやインタフェースのことでまちがいないでしょうか? 2:「生成処理はnewを直接記述するためクラスどうしが強く結合する。利用者が、ポリモーフィズムを働かせるために、サブクラスを使おうとすれば、自分でnewを書き換えなければならない。これは、ポリモーフィズムなど使わず、直接サブクラスを使うことと変わらない。」 ここがよくわかりませんでした。 Animal <- Dog のような継承構造があった場合、 Animal pet = new Dog(); とするのと、 Dog pet = new Dog(); とするのは変わらないということでしょうか? しかし、型をAnimalにすることで、使用処理の方でポリモーフィズムを支えることができると思うのです。 3:「使用処理と生成処理を同じコードに書くと、ポリモーフィズムを使いたいのに自ら使えなくしていることになる。」 ここもよくわかりませんでした。 同じコードに書く、とは Animal myPet = new Dog(); のようにnewをハードコーディングするということですよね? そうしても、ポリモーフィズムを使えないとは思えないのですが。。 4: ファクトリの目的は凝集度を高め、生成処理の弱点を克服することとありましたが、生成処理の弱点とは何なのでしょうか? 私の考えは、通常のクラスをたくさん作ってしまい、ファクトリーメソッドなどを使わなかった場合、すべてのクラスを(アクセス修飾子がpublicであれば)自由にインスタンス化できてしまうが、ファクトリーメソッドを使った場合、それを隠蔽できる。 vornan19さんがおっしゃっている生成処理の弱点というのは「自由にインスタンス化できてしまう点」でしょうか?
退会済みユーザー

退会済みユーザー

2016/10/02 13:39

1. ジェネリックの意味はその通りです。2.のAnimal pet = new Dog();は、=をはさんで、<使用処理> = <作成処理>になっています。これが使用処理と作成処理が同じコードに書かれていることですね。<使用処理>はAnimal型ならなんでも良いと言っているのに、<作成処理>はDogしか作らない(他の型はつくれない)としているのです。理由は、ひとつのnewは特定のクラスのインスタンスしか作れないから。このように、自家消費するなら<使用処理>はDogしかありえずAnimalは無意味です。3.も同じことの言い換えです。2と3は「生成処理をいじると使用処理が影響を受けるというのがしっくりきません。」の答えですが、<使用処理> = <作成処理>と記述するなら「つねに<作成処理>が型を支配する」と言いたかったわけです。説明が下手なのはご容赦ください。4. はnewというコードはいったん書くと変更できない。閉ざされた世界で凝集度を高める場合は、newしても問題ないけれど、公開された空間でnewを使えるようにすると、あとで別の型を使いたくなたったら、そこを書き換えなければならない。書き換えのコストが高いという意味です。言い換えるとnewはクラスの結合度を高めるので保守性が悪い。
退会済みユーザー

退会済みユーザー

2016/10/05 01:50

回答ありがとうございました。
guest

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
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/10/01 13:55

回答ありがとうございます。 お話自体は興味深く、見させていただき、大変勉強になったのですが、お示ししたサイトのイントロダクションはそのようなことを言っているのでしょうか? インタフェースのお話は出ていなかったのですが、生成処理と使用処理を分離するためにはインタフェースを利用することが多い、もしくは利用しなければならない、ということでしょうか? そして、インタフェースを型として利用しておけば、使用処理は生成処理の変更に影響を受けなくなる、という解釈でよろしいでしょうか? また、ちょっとした疑問なのですが、KSwordOfHasteさんの回答では、インタフェースがファクトリーの機能を担っていますが、Aが別のクラスを追加する可能性も考えると、ファクトリーの部分はswitch文などにした方がいいのではないかと思ったのですが、どうでしょうか?
KSwordOfHaste

2016/10/01 15:20

インターフェースでも抽象クラスでも具象クラスでもいずれでも構いません。ポイントはクラスを利用者(自分自身のこともあります)に提供した後、提供側の都合でクラスが追加になっても利用者側のコードが影響を受けない点です。switch文にするかどうかは本トピックのポイントではないですが、「もし後から変更になるとしたらどんな実装がよさそうか」には関係しますね。クラスの切り分けがenumなどによるものであればswitch文が適切だと思います。一方私の例では値の範囲でクラスを切り替えるので最初はif文がよさそうです。8文字未満ならAInt,15文字未満ならALongそれ以上ならABigIntegerなどとなった場合、if文を2回判定するのがいやだという理由でswitch文に直すことはありそうですが。いずれにせよ一概にswitchがよいとはいえません。
退会済みユーザー

退会済みユーザー

2016/10/05 01:49

とても分かり易かったです。 回答ありがとうございました。
guest

0

          

投稿2016/09/28 17:27

編集2016/09/29 15:08
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問