web-dev-qa-db-ja.com

カプセル化にあまり違反しないでC ++のクラス間でデータメンバーを共有する方法

C++では、クラスAがあるとします。

_Class A
      {
      int a1, a2, a3;

      void foo();
      }
_

2番目のクラスBのメンバー関数でメンバーのサブセット(a1、a2)を使用する必要があります。

Aへのポインターを引数として渡すことによってBのメンバー関数の引数を定義する必要があるのか​​、またはAのメンバーを引数として渡す必要があるのか​​と思います。例えば。、

_Class B
      {
      int b1, b2;

      void bar(A &a);
      }
_

またはこれは

_Class B
      {
      int b1, b2;

      void bar(int a1, int a2);
      }
_

後者は、カプセル化に違反する必要性を最小限に抑えるように思われます。これは、BがAの構成要素に大きく依存しないためです。一方、前者の場合、BはAのメンバーについて何かを知っている必要があります。そのため、2番目の実装の方が望ましいと思われます。

最初の実装に問題があると思われるもう1つの理由は、理想的には、Aのメンバー(a1、a2、a3)を公開ではなく保護したいということです。通常、私はBをAのフレンドクラスにしたいと思っていますが、ここでの問題は、AとBの両方にいくつかの派生クラスがあることです。解決。

したがって、これらの理由は、Bの2番目の定義のようなものを使用することを主張することになります。しかし、私が扱っている実際のコードでは、Bが必要とするAのメンバーがかなり少ない(約6以上)ので、これにより扱いにくい。ポインターをAに渡すだけで読みやすくすることは、読みやすさの観点からは望ましいと思われ、Bを扱うコードの他の部分からB.bar()に必要な詳細を隠します。

おそらく、回避策の1つは、A内に次のようなメソッドを含めることです:A::get_a1() {return this->a1}または何かがここで本当に正しい設計ソリューションであるかどうかもわかりません。

5
user1790399

ただし、私が扱っている実際のコードでは、Bが必要とするAのメンバーがかなり(約6以上)いるため、扱いにくい場合があります。

クラスAからクラスBに転送するメンバーを保持する Data Transfer クラスを作成し、新しいクラスのインスタンスを取得するようにクラスBにメソッドを記述します。

5
Robert Harvey

複数の場所にa1a2が必要な場合は特に、これを1レベル上に抽象化することをお勧めします。手順は次のようになります。

  1. 関数が必要とするデータを特定します。

  2. 概念的には、データを独自の単位にグループ化します。

  3. データを説明してください。このデータは何を表していますか?

  4. インターフェースを作成する1 データの説明にちなんで名付けられました。

これで、クラスAのインターフェイスを継承し、クラスBの関数が新しいインターフェイスへの参照を取得します。

利点:

  1. 疎結合 :あなたdecouple 2つのクラス。 BAが持つデータについて知る必要があるが、それらが密結合されていることが適切でない場合、インターフェースはデザインをクリーンに保つ抽象化のレイヤーを追加します(少し大きい場合でも) )。

  2. コードの再利用 :インターフェースのデータが他の場所に適用できる場合、インターフェースを再利用できます。

  3. インターフェースの特定の使用法が、データ転送クラスの使用に関するRobert Harveyの提案により適している場合は、インターフェース(純粋な仮想クラス)の必要最小限の実装を作成して使用できます。この2つのクラスは、インターフェイスのユーザーに関する限り互換性があり、柔軟性が向上します。

コード例:

class Foo {
public:
  int getA1() const = 0;
  int getA2() const = 0;
};

class A : public Foo {
  int a1, a2, a3;
public:
  // C++11 adds the "override" keyword.
  int getA1() const override { return a1; }
  int getA2() const override { return a2; }
  int getA3() const { return a3; }
}

class B {
public:
  void foo(const Foo& foo) {
    foo.getA1();
    foo.getA2();
  }
}

b.foo(a);

注:継承を介して実装するのではなく、コンポジションを介してAFooを含めることも有効な場合があります。これはおそらくより良い解決策ですが、あなたの質問の文言に基づいて、私は上記の解決策がより適切であると感じました。以下に、完全を期すためのコード例を示します。

class Foo {
  int a1, a2;
public:
  int getA1() const { return a1; }
  int getA2() const { return a2; }
};

class A {
  int a3;
  Foo foo;
public:
  const Foo& getFoo() const { return foo; }
  int getA3() const { return a3; }
}

class B {
public:
  void foo(const Foo& foo) {
    foo.getA1();
    foo.getA2();
  }
}

b.foo(a.getFoo());

1 C++には、他の特定の言語のようにinterfaceキーワードがありません。 abstractまたは純粋なvirtualクラスの概念もありません。これらの概念は、クラスのfunctionsにのみ適用されます。この文脈では、C++インターフェースとは、すべての関数が純粋なvirtualであるクラスを意味します。唯一の例外はコンストラクタとデストラクタです。これらはvirtualにすることはできず、コンパイラが生成する場合でも存在する必要があります。また、デストラクタmustは、サブクラスを持つように設計されたすべてのクラスで仮想として宣言されることに注意してください。

3
user22815

@Robert Harveyによって提案されたような余分な「データ転送クラス」を回避したい場合は、ゲッター関数とセッター関数によってメンバー変数にアクセスする必要があります。
このように、前の段落で説明したアプローチ

おそらく1つの回避策は、A :: get_a1(){return this-> a1}か何かを行うA内のメソッドを持つことですが、それが本当に正しい設計ソリューションであるかどうかもわかりません。

私にとって正しい解決策です。

ただし、それぞれに多数のメンバー変数を含む数十のクラスを作成しました(ただし、C#では、根本的な問題は同じです)。また、あなたと同じように、これらのメンバー変数にアクセスする必要がありました。あなたは私が見ることができる限り読む必要があります。私も書いています。
私のソリューションは、各メンバーのget/set関数のペアではありませんでした(つまり、nメンバーの場合、2 * n関数を作成する必要があります)、1つのget関数と1つのset関数(または1つの各変数タイプの取得/設定)。設定する必要がある変数の選択は、次の関数で整変数(正確にはEnum、PID、パラメーターID)を渡すことによって行われます。

int GetValue (EnumPID i_enPID, out int    o_iValue);
int GetValue (EnumPID i_enPID, out double o_dValue);
int GetValue (EnumPID i_enPID, out String o_sValue);  // C#
...
int SetValue (EnumPID i_enPID, int    i_iValue);
int SetValue (EnumPID i_enPID, double i_dValue);
int SetValue (EnumPID i_enPID, String i_sValue);  // C#
...
// the int return value is the result of the function: SUCCESS (=1), failure (= 1001 upwards).
// Actually, it's an Enum in my case, too, (EnumResult)...

このソリューションの功績は、私にこの種のデザインを見せてくれた前の上司にあります。
それ以降、C#で完全に動作しています。

0
Tobias Knauss