私はいくつかの大きな(3つ以上のフィールド)オブジェクトを持っています。そのような場合に遭遇するたびに、長いパラメーターリストを使用してコンストラクターのabominationを作成する傾向があります。
違和感があり、使いづらく、読みやすさが低下します。
フィールドがリストのようなある種のコレクション型である場合はさらに悪いことです。単純なaddSibling(S s)
を使用すると、オブジェクトの作成が非常に簡単になりますが、オブジェクトは変更可能になります。
このような場合、皆さんは何を使用しますか?
私はScala=およびJavaを使用していますが、言語がオブジェクト指向である限り、問題は言語にとらわれないものだと思います。
私が考えることができるソリューション:
さて、あなたは一度読みやすく、作成された不変オブジェクトの両方を望みますか?
流暢なインターフェース正しく完了が役立つと思います。
これは次のようになります(完全に構成された例)。
final Foo immutable = FooFactory.create()
.whereRangeConstraintsAre(100,300)
.withColor(Color.BLUE)
.withArea(234)
.withInterspacing(12)
.build();
ほとんどのJavaプログラマーは流暢なインターフェースを間違って取得し、オブジェクトの構築に必要なメソッドでオブジェクトを汚染するため、 "CORRECTLY DONE"を太字で記述しました。もちろんこれは完全に間違っています。
コツは、build()メソッドだけが実際にFooを作成することです(したがって、Fooは不変である可能性があります)。
FooFactory.create()、whereXXX(..)およびwithXXX(..)すべてが「何か他のもの」を作成します。
それ以外の何かはFooFactoryかもしれません。これを行う1つの方法を次に示します。
FooFactoryは次のようになります。
// Notice the private FooFactory constructor
private FooFactory() {
}
public static FooFactory create() {
return new FooFactory();
}
public FooFactory withColor( final Color col ) {
this.color = color;
return this;
}
public Foo build() {
return new FooImpl( color, and, all, the, other, parameters, go, here );
}
Scala 2.8では、名前付きパラメータとデフォルトパラメータ、およびcopy
メソッドをケースクラスで使用できます。コード例は次のとおりです:
case class Person(name: String, age: Int, children: List[Person] = List()) {
def addChild(p: Person) = copy(children = p :: this.children)
}
val parent = Person(name = "Bob", age = 55)
.addChild(Person("Lisa", 23))
.addChild(Person("Peter", 16))
まあ、Scala 2.8:
case class Person(name: String,
married: Boolean = false,
espouse: Option[String] = None,
children: Set[String] = Set.empty) {
def marriedTo(whom: String) = this.copy(married = true, espouse = Some(whom))
def addChild(whom: String) = this.copy(children = children + whom)
}
scala> Person("Joseph").marriedTo("Mary").addChild("Jesus")
res1: Person = Person(Joseph,true,Some(Mary),Set(Jesus))
もちろん、これには問題があります。たとえば、espouse
とOption[Person]
を作成して、2人が結婚するようにします。 private var
および/またはprivate
コンストラクターとファクトリーのいずれかに頼らずにそれを解決する方法は考えられません。
さらにいくつかのオプションがあります:
実装自体を変更可能にしますが、変更可能および不変に公開するインターフェースを分離します。これは、Swingライブラリデザインから取得されます。
public interface Foo {
X getX();
Y getY();
}
public interface MutableFoo extends Foo {
void setX(X x);
void setY(Y y);
}
public class FooImpl implements MutableFoo {...}
public SomeClassThatUsesFoo {
public Foo makeFoo(...) {
MutableFoo ret = new MutableFoo...
ret.setX(...);
ret.setY(...);
return ret; // As Foo, not MutableFoo
}
}
アプリケーションに不変のオブジェクトの大規模で事前定義されたセット(構成オブジェクトなど)が含まれている場合は、 Spring フレームワークの使用を検討してください。
異なる種類不変性 があることを覚えておくと役立ちます。あなたの場合、私は「ポプシクル」不変性が本当にうまくいくと思います:
ポプシクルの不変性:気まぐれに追記型の不変性をわずかに弱めていると私は思います。初期化中にしばらく変更可能であり、その後永久に「凍結」されたオブジェクトまたはフィールドを想像できます。この種類の不変性は、相互に循環参照する不変オブジェクト、またはディスクにシリアル化され、逆シリアル化の際に、逆シリアル化プロセス全体が完了するまで「流体」である必要がある不変オブジェクトに特に役立ちます。フローズン。
したがって、オブジェクトを初期化してから、何らかの「フリーズ」フラグを設定し、書き込み不可であることを示します。できれば、ミューテーションを関数の背後に隠して、関数がAPIを使用するクライアントに対して純粋であることを確認します。
4つの可能性を検討します。
new Immutable(one, fish, two, fish, red, fish, blue, fish); /*1 */
params = new ImmutableParameters(); /*2 */
params.setType("fowl");
new Immutable(params);
factory = new ImmutableFactory(); /*3 */
factory.setType("fish");
factory.getInstance();
Immutable boringImmutable = new Immutable(); /* 4 */
Immutable lessBoring = boringImmutable.setType("vegetable");
私にとって、2、3、4はそれぞれ異なる状況に適応しています。最初のものは、OPで引用されている理由から、愛するのが難しいものであり、一般的に、クリープが発生し、リファクタリングが必要なデザインの症状です。
(2)としてリストしているのは、「ファクトリー」の背後に状態がない場合に適していますが、(3)は状態がある場合に選択する設計です。スレッドや同期を気にしたくない場合、(3)ではなく(2)を使用していることに気付き、多くのオブジェクトの生成に対していくつかの高価な設定を償却することを心配する必要はありません。一方、(3)は、実際の作業が工場の建設に入るときに呼び出されます(SPIからのセットアップ、構成ファイルの読み取りなど)。
最後に、他の誰かの答えがオプション(4)に言及しました。この場合、小さな不変オブジェクトがたくさんあり、好ましいパターンは古いオブジェクトからニュースを取得することです。
私は「パターンファンクラブ」のメンバーではないことに注意してください。確かに、エミュレートする価値があるものもありますが、人々が名前や面白い帽子を与えると、彼らは自分の役に立たない人生を歩んでいるようです。
また、不変オブジェクトにミューテーター(addSiblingなど)のように見えるメソッドを公開させ、新しいインスタンスを返すようにすることもできます。それが不変のScalaコレクションが行うことです。
欠点は、必要以上のインスタンスを作成する可能性があることです。また、部分的に構築されたオブジェクトを処理したくない場合を除いて、有効な中間構成が存在する場合(ほとんどの場合、兄弟がないノードの場合など)にのみ適用されます。
たとえば、まだ宛先がないグラフエッジは、有効なグラフエッジではありません。
もう1つの潜在的なオプションは、構成可能なフィールドが少なくなるようにリファクタリングすることです。フィールドのグループが(ほとんど)互いに動作するだけの場合は、それらを独自の小さな不変オブジェクトにまとめます。この「小さい」オブジェクトのコンストラクタ/ビルダーは、この「大きい」オブジェクトのコンストラクタ/ビルダーと同様に、より管理しやすくなるはずです。
私はC#を使用していますが、これらは私のアプローチです。検討してください:
_class Foo
{
// private fields only to be written inside a constructor
private readonly int i;
private readonly string s;
private readonly Bar b;
// public getter properties
public int I { get { return i; } }
// etc.
}
_
オプション1。オプションのパラメーターを持つコンストラクター
_public Foo(int i = 0, string s = "bla", Bar b = null)
{
this.i = i;
this.s = s;
this.b = b;
}
_
例として使用new Foo(5, b: new Bar(whatever))
。 Javaまたは4.0より前のC#バージョンの場合は対象外ですが、すべてのソリューションが言語にとらわれない例であるので、表示する価値があります。
オプション2。単一のパラメータオブジェクトを取るコンストラクタ
_public Foo(FooParameters parameters)
{
this.i = parameters.I;
// etc.
}
class FooParameters
{
// public properties with automatically generated private backing fields
public int I { get; set; }
public string S { get; set; }
public Bar B { get; set; }
// All properties are public, so we don't need a full constructor.
// For convenience, you could include some commonly used initialization
// patterns as additional constructors.
public FooParameters() { }
}
_
使用例:
_FooParameters fp = new FooParameters();
fp.I = 5;
fp.S = "bla";
fp.B = new Bar();
Foo f = new Foo(fp);`
_
3.0以降のC#では、オブジェクト初期化子構文を使用してこれをよりエレガントにしています(セマンティックは前の例と同等です)。
_FooParameters fp = new FooParameters { I = 5, S = "bla", B = new Bar() };
Foo f = new Foo(fp);
_
オプション3:
このような膨大な数のパラメーターを必要としないようにクラスを再設計します。その責任を複数のクラスに分割できます。または、必要に応じて、コンストラクターではなく特定のメソッドにのみパラメーターを渡します。常に実行可能なわけではありませんが、実行可能な場合は、実行する価値があります。