2つのクラスを想像してください。
class Person {
MarriedPerson marry(Person other) {...}
}
class MarriedPerson extends Person {
}
これは、メソッドPerson
が呼び出されたときに、MarriedPerson
が自動的にmarry
と入力するように「モーフィング」されるという考え方です。このため、MarriedPerson
のインスタンスを作成し、最初のPerson
オブジェクトからデータを転送する必要があります。 Person
オブジェクトはまだ存在しますが、これは悪いことです。別の方法は、デザインパターンの適用です...
これは宿題ではありません。コメントしてから気になった here 。
これは、実際にアイデアを実演するためのかなり不自然な例であることを知っています。しかし、経験則として、サブタイプのポリモーフィズムに依存する前に、可能なすべてのオプションを使い果たしたいと思います。ここでは、ドメインに基づいた構成を使用する方が優れたソリューションです。
結婚は人種、富、利害などを超えて人の「タイプ」を定義するものではありません。したがって、このケースをモデル化するためにサブタイプ多態性を使用することは適切ではありません。より技術的なレベルでは、ほとんどの言語は単一継承のみをサポートしています。したがって、MarriedPerson
、PacificIslanderPerson
、およびWealthyPerson
のクラスがある場合、これらを一緒に「構成」して、裕福な既婚の太平洋諸島人を記述する方法はありません。
代わりに、Person
クラス内で単純な構成を使用します。
public class Person {
public MarriageStatus marriageStatus;
public Race race;
public Wealth wealth;
}
ここで、MarriageStatus
、Race
、およびWealth
はすべて単一の責任を持つことができ、おそらくかなり単純です。 MarriageStatusの例は次のとおりです。
public class MarriageStatus {
public Datetime anniversary;
public Person husband;
public Person wife;
// TODO: In the future the stakeholder would like to support polyamory
// public List<Person> spouses;
}
HaskellやRust with traits(Haskell用語の型クラス))などのプログラミング言語を使用している場合は、Personを関数の観点から自動的にMarriedPersonのように動作させることができます。 OOP言語の場合、ビジネスロジックはMarriageStatus
、Race
、およびWealth
オブジェクトでのみ機能します。これらはPerson
これら3つの合成プロパティ間の相互作用が必要な場合。
このようにして、再帰的な関係とそのすべての落とし穴から自分を設計しました。
質問のポイントを完全に逃してしまったことをお詫び申し上げます。具体的に言うと
Personオブジェクトはまだ存在しますが、これは悪いことです。
それは必ずしも本当だとは思いません。 MarriedPersonオブジェクトを返し、元の "Person"への参照がない場合、ガベージコレクターは古い "Person"オブジェクトを削除します。私はあなたを誤解しているかもしれません。
person
は、結婚しているかどうかにかかわらず、同じ人物です。コピー置換アプローチでそれを別の種類のperson
にモーフィングしても、この基本的なアイデンティティは維持されません。
他のいくつかの代替案を検討することができます:
状態パターン をここで適用できます。しかし、関係をモデル化するより良い方法があるかどうかは、使用するコードのニーズに完全に依存します。使用するコードを提供していないので、divorce()
やfileTaxes()
などの関数を想像するしかありません。しかし、それらがあなたのニーズに合っているかどうかはわかりません。
使用という観点からは、自分でデザインをするのがとても好きです。これら2つのクラスから始めて、それらのすべての使用法を想像しようとすると、多くのオーバーデザインを招きます。
これらのクラスもあるかもしれないさまざまなニーズに適用される他のパターンを想像することができますが、それに対する妥当な終わりが見当たらないので、ここで停止すると思います。
MarriedPersonは、実際にはPersonが実装できない可能性のある異なる動作を示しますか?いいえの場合、クラスは必要ありません。
その上、私は人の内に人の結婚ステータスを置くことを避けます。 2つのPersonオブジェクトが関係するMarriageオブジェクトを用意し、そのMarriageを適切な台帳に入れます。そうすれば、一貫性のあるデータを確保するのが簡単になります。
サブタイプのポリモーフィズムはこの特定の問題に対する誤った解決策であり、さらに一般的には、それが通常は最善の解決策ではないという他の回答にも同意します。
とはいえ、isサブタイプ多態性を使用したこの問題の標準的な解決策があり、他の状況で役立つ場合があります。このソリューションは、最初はJames Coplienによって正式に説明され、さまざまな名前で呼ばれていますが、最も一般的に知られているのは envelope/letter idiom /です。 (取り出して、同じ人宛の異なる内容の手紙と交換できる手紙が入った封筒に似ているため)。
両方のクラス(例ではPerson
とMarriedPerson
)は同じ抽象基本クラスの具体的な実装(「文字」)であり、両方にでアクセスします一般に同じインターフェイスも実装し、内部的にレタークラスのインスタンスに委ねるエンベロープクラス。
関係をより明確にするために、以下ではPerson
をUnmarriedPerson
に名前変更し、Person
をエンベロープクラスの名前として使用しました。
interface AbstractPerson {
String name();
boolean married();
}
class UnmarriedPerson implements AbstractPerson {
private final String name;
UnmarriedPerson(final String name) { this.name = name; }
public String name() { return name; }
public boolean married() { return false; }
}
class MarriedPerson implements AbstractPerson {
private final String name;
MarriedPerson(final String name) { this.name = name; }
public String name() { return name; }
public boolean married() { return true; }
}
class Person implements AbstractPerson {
private AbstractPerson handle;
public Person(final String name) { handle = new UnmarriedPerson(name); }
public String name() { return handle.name(); }
public boolean married() { return handle.married(); }
public void marry(Person other) {
if (married() || other.married()) {
throw new IllegalStateException("already married");
}
setMarried();
other.setMarried();
}
private void setMarried() { handle = new MarriedPerson(name()); }
}
class Main {
static void print(final Person p) {
System.out.printf("%s is %s\n", p.name(), p.married() ? "married" : "single");
}
public static void main(String[] args) {
final Person sue = new Person("Sue");
final Person andy = new Person("Andy");
print(sue); // “Sue is single”
print(andy); // “Andy is single”
sue.marry(andy);
print(sue); // “Sue is married”
print(andy); // “Andy is married”
}
}
ただし、他の回答で述べられているすべての理由により、この特定のケースではこのソリューションを使用しないでください/すべきではありません繰り返します。封筒/手紙のイディオムが良い解決策である具体的な例を考えるのはかなり難しいです:それで「実際の状況」をモデル化しようと試みることはおそらくお勧めしません。
代わりに、本当の用途は、抽象データ型に動的にスワップアウトする必要がある複数の実装がある場合です。データの特性に応じてMatrix
またはSparseMatrix
として実装できるDenseMatrix
クラスなど、最適な実装戦略が実行後に変更される可能性がある場所行列の操作。
人が結婚すると、本来の人物の正確なコピーである結婚した人物が作成され、元の人物が破棄されると主張することができます。
幸いなことに、オブジェクト指向の世界は、現実の世界よりもはるかに論理的であることがよくあります。この場合、MarriedPersonクラスが存在する理由はまったくわかりません。このクラスがPersonクラスと異なるのは正確には何ですか?
元のクラスの状態を区別するためだけにサブタイピングすることは、一般に非常に悪い考えです。変更する必要のある属性を元のクラスに追加するだけでよいのです。
結婚したらその人に行動を変えてもらいたい場合は、訪問者パターンを使用してその行動を実装します。次に、結婚行為は、どの訪問者が適切な行動を実行したかを切り替えます。
確かに、オブジェクトが動的に動作を変更したい場合があります。しかし、これは戦略またはビジターパターンのいずれかを介して行うことができます。