web-dev-qa-db-ja.com

内部メンバーのインターフェースを選択的に公開

固定サイズの2D配列を表すD2Arrayというクラスがあります。それは汎用的であることを意図しており、要素の取得、要素の設定、行全体、列全体の抽出など、非常に多くのメソッドが付属しています。

次に、D2Arrayを含むMyClassを記述します。MyClassを適切に使用するには、内部配列の状態を操作する必要があります。簡単な解決策は、配列へのゲッターを用意し、MyClassの外で操作してから、セッターを通じて配列に戻すことです。

(通常の代わりにTemplate<Type>表記、私は使用しましたTemplate-Typeテンプレートパラメータを説明するために、これはちょっとした仕掛けですが、StarUMLでは、それ以外の方法で行うことはできません。)

Diagram describing the above

ただし、この方法では、すべてのD2Arrayをセッターに送ることができ、無効な状態の発生を防ぐために何らかの検証が必要になります。私の場合、これはすぐに高価になる可能性があり、セッターがスローする可能性があることは恐ろしく直感に反するため、これは明らかに適切な解決策ではないと思います。


カプセル化を強力に実施するという利点もある別の簡単なソリューション:MyClassで、内部メソッドを操作するパブリックメソッドを記述します。

Diagram describing the above

簡単ではない方法でのみ配列の操作を許可する必要がある場合、これは実際には問題ありません。ただし、配列の既存のメソッドの一部または全体を公開するだけの場合は、適切なメソッドに引数を転送するだけの1行の文字を書き込むことになります。 1つのクラスでこれを行う必要があることは既に鈍感に感じています。ましてや、すべて同じように異なる方法で内部配列を使用するいくつかのクラスでそれを行うことはもちろんです。

過去にさかのぼって、これが私の問題の本質だと思います。内部配列のインターフェースのサブセットのみを正確にそのまま公開し、それを行うために最小限の転送メソッドを記述しなければならず、その内容をサブセットは、内部配列を使用するクラスのニーズにも依存します。


私は次の解決策を思い付きました。これは私の要件にうまく適合しているようですが、それがアンチパターンの種類ではないかと思っています。

  • 保護されたメンバーとして内部配列を含み、すべての内部D2ArrayWrapperメソッドへの転送メソッドを公に宣言および定義する、新しいクラスD2Array(例ではダム名)を記述します。
  • MyClassD2ArrayWrapperから継承するようにします。このように、内部の2D配列が含まれており、すべての転送メソッドがすでに実装されているので、自分で書く手間が省けます。 MyClass2も管理するD2Arrayを記述したい場合、このゲインは2倍になります。D2ArrayWrapperから継承するだけです。
  • 配列メソッドにMyClassの外部からアクセスできないようにするには、保護されたメンバーまたはプライベートメンバーとしてMyClassから再宣言する必要があります。

Diagram describing the above

好きな理由:

  • それは私が欲しかったものをある程度達成します。

嫌いな理由:

  • MyClassからアクセスできるようにする配列メソッドを再宣言して再定義する代わりに、配列メソッドを再宣言しますしないMyClassからアクセスできるようにしたい。私のDRY問題は解消されず、単純に逆転しました(C++では、単純なusingステートメントでうまくいきますが、Javaでは、完全な再定義が必要です)。
  • 多重継承を処理する言語でのみ機能します。単一継承のみをサポートする言語でも機能しますが、そのクラスの継承の可能性を無駄にしてしまいます。これは、望ましくない大きな副作用であることがわかりました。
  • 元の目標の単純さと比較すると、やりすぎだと感じます。

どちらの解決策をとるべきか、今はちょっと困っています。
検証1が複雑になるため、ソリューション1は実行できません。
ソリューション2は問題ありませんが、無意味でやや上品ではありません。
ソリューション3は、手に負えなくなった事態の複雑化のように感じられ、少し考えた結果、私がやりたいことではないと確信しています。

その結果、ソリューション2を使用することを検討していますが、別の方法はありませんか?
ありがとうございます。

5
qreon

私が正しく理解している場合は、内部配列の一部の操作をサポートし、他の操作はサポートしないことをお勧めします。このサポートされている操作のセットが、D2ArrayWrapperが理解するのに十分一般的である場合は、インターフェイスを使用することをお勧めします。

たとえば、ユーザーが配列からセルと行を読み取れるようにしたいが、書き込みはできないようにするとします。

interface IReadOnlyD2Array<T>
{
   T getCell(int row, int col);
   IReadOnlyList<T> getRow(int row);
   IReadOnlyList<T> getColumn(int col);
}

それはC#にありますが、同じ原則がすべてのOO言語に適用されます。次に、D2Arrayにそのインターフェイスを実装させ、代わりにそれを介してそれを返します。

private D2Array<T> _internalArray;
public IReadOnlyD2Array<T> Array => _internalArray;

一般向けにライブラリを実装している場合は、読み取り専用の性質を強制するラッパーを作成して、人々がそれをD2Arrayに強制的にキャストできないようにすることができますが、それ以外の場合は、そうしないでください。

-

関連するクラスを変更する機能がない場合、またはC++を使用していて、仮想呼び出しのオーバーヘッドを望まない場合の代替アプローチは、D2Arrayをカプセル化し、メソッドのみを公開する「ビュー」クラスを作成することです。欲しいです:

template <typename T>
class D2ArrayView 
{
   public:
      D2ArrayView(const D2Array<T> &srcArray) : array(srcArray) { }

      T getCell(int row, int col) const { return array.getCell(row,col); }
      // etc ...

   private:
      const D2Array<T> &array;
}
5
Errorsatz