QA@IT

ネストした関係性の一番奥のメンバ変数に対しての、引数の渡し方

2604 PV

抽象的な質問となりますが、javaやC#といった言語で、ネストした関係性のメンバ変数に対しての、引数の渡し方で悩んでいます。

「Aのメンバ変数であるB」、「Bのメンバ変数であるC」という関係性のクラスがあるとします。
もしこれをinitializeする場合、「Aのinitializeの呼び出しもと」からCに値を渡す為には、関係ない値であるにも関わらず、A,Bのinitializerにもその引数の値を書かなければいけません。

抽象的で申し訳ないのですが、こういった問題を解決する方法はあるのでしょうか?無いというご回答でも助かります。
ご回答いただけると幸いです。

回答

何に依って決まるかと、
関係有無よりも、A,Bはその値を知っていてよいかなどにも依ると思いますが
(逆に誰ならそれを知っていてよいのか)

こんなようなコードの話ですよね?
(コードはC#風に書いていますが適当なので、雰囲気だけ参考にしてください)

public class A{
  public A(B b){  this.b = b; }
}
public class B{
  public B(int valForC){
    this.c = new C(valForC);
  }
}
public class C{
  public C(int val){ }
}

void main(){
  int valueForC = 0;
  var  a = new A(new B(valueForC));
}

考えられる単純なものとしては
1) Cが自発的に値をどこかから取得する

全部は書き直しませんが、以下のようなstaticメソッドを用意すればA,BはCのコンストラクタ引数からは解放されますね。

public class C{
  public C(){ this.val = SystemValueManager.GetValueForC();  }
}

2) CのFactoryクラス、Factoryメソッドを用意する

例えばBでFactoryクラスからCを作成したり、

public class B{
  public B(){ this.c = CClassFactory.Create();  }
}

またはC自体がFactoryメソッドをもっていたり

public class B{
  public B(){ this.c = C.Create();  }
}

できれば、値からは解放されます。

3) 値ではなくラムダ式など関数オブジェクトを渡せる様にしておく。

例えば

public class A{
  public A(Action cGenerator){
    this.b = new B(cGenerator);
  }
}

public class B{
  public B(Action cGenerator){
    this.c = cGenerator();
  }
}

public class C{
  public C(int value){
    this.value = value;
  }
}

void main(){
  var a = new A( () => new C(0) );
}

とすると、値が何か(必要かどうかも含めて)はA, Bは知らなくて済みます。
しかしこの場合は AはCを生成するメソッドを受け取ってしまうので間接的にCとの関連性を切れてませんね。

※ ただし最初の例の様にActionを受け取るのがBのコンストラクタのみにすれば、AとCの関連性はなくせます。

4) あとはちょっと面倒ですがインターフェースやファクトリを用意してBとCの依存が逆転するようにしむけるという手もあるかと思います。2のFactoryクラスも手抜きでstaticにしてしまいましたが、Factoryクラスのインスタンスを用意する場合はインスタンスをどこで生成するかも考える必要が出てきます。

追記

ついでに書いてみました。
public staticなInstanceメンバ変数とか、そもそも1枚(1パッケージ)でやると元々の狙いからも…などいろいろありますが、
あくまで例ということでお願いします。

public class A{
  public A(){
    this.b = new B();
  }
}

public class B{
  IBC c;

  public B(){
    this.c = IBCFactory.Instance.CreateNewIBCConcrete();
  }
}

public interface IBC{
  int Method1();
}

public class C : IBC{
  public C(int value){
    this.value = value;
  }

  public int Method1(){
    return value;
  }
}

public class IBCFactory{
  public static IBCFactory Instance;
  private int value;

  public IBCFactory(int value){ this.value = value; }
  public IBC CreateNewIBCConcrete(){
    return new C(value);
  }
}

void main(){
  IBCFactory.Instance = new IBCFactory(0);
  var a = new A();
}
編集 履歴 (3)
  • 詳しいご回答、いつもありがとうございます。また、もう少し具体的な例をのせるべきでした、すいませんでした。

    なるほど、staticを用いてFactory等を使ってうまくやるか、関数オブジェクトを使うかということですよね。

    自分の場合にどのパターンがあっているか再度考えてみようと思います。ありがとうございました。
    -
  • Factoryクラス、メソッドは簡単のためにstaticにしましたが生成対象を複数種類または同じものを複数回生成しにくくなるなどstaticではない方がいい場合がありますね。どこかで生成はしないといけないので、CまたはCに渡す値を詳しく知っていて良いオブジェクトが何なのか考えられるといいと思います。 -

Aのコンストラクションで内部のB,Cも生成するということはB,CはAの実装のためだけに存在しているということが多いと思います。C,C++,(C#も?)では参照ではなく実体そのものを別のクラスに直接おくことができますがそういう場合は間違いなく保持されているクラスの生存期間はそれを保持しているクラスと一致しますね。

そういう関係のクラスではB,CはAを実装するためにAが都合がよいから使っているのであって別に外部にB,Cの存在を意識させなくてよいことも多いと思います。

そうして設計したAクラスでは、実際にBやCのコンストラクターにパラメータが必須だったとしてもそれをAクラスの使用者に決めさせるのではなくAクラスが自身で決めるべきと考えます。Aの使用者にとってBなどのパラメータが指定したくなることはないはずです。存在を意識しないのですから。もしAがBを初期化する際のパラメータを外部から選択させたいと思うならそれはもはやBクラスのパラメータではなくAクラス(の振る舞いを指定するための)A自身のパラメータです。

こういったとき自分はBの初期化パラメータを変えることでAの使用者にとってどういう機能として見せたいのかを考え必要ならAクラスのオプションとして別に定義したりします。

Aの機能とは?ということを考えることでB,Cの内部クラスのパラメータを個別に指定させるより、より抽象度が高く数も少ないA自身のパラメータ設計が見えてきたりします。

一方、Aの中にBが保持されていることを外部から意識させるほうが適切なこともあると思います。例えば楽譜Score、それを演奏するScorePlayer、それを表示するScoreViewerといったものを考えるとそれぞれのインスタンスの生存期間は必ずしも一致せずそれぞれアプリケーションは独立したインスタンスとして意識するでしょう。そういった場合は内部にScoreを保持していたとしてもScorePlayerのコンストラクターでScoreのコンストラクターを呼ぶことは考えにくくSocrePlayerのコンストラクターにはScoreのインスタンスそのものを指定させる方が自然です。

必ずしもいつもどちらかに整理できるわけでもないかも知れませんが、自分は大抵上記の2つのパターンにあてはめてどちらかで考えるようにしています。

ただしカスタマイズの利便性のために本来は外部に意識させたくない内部クラスをの振る舞いを外部から指定させたくなる場合もあります。そういうときはFactory、AttributeSetのようなものを使って「特別に意識したいときだけできるだけ少ないパラメータ数でまとめて指定」できるような作りで妥協します。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。