- ベストアンサー
オブジェクト指向における「クラスA have a クラスB」の関係において,クラスBからクラスAのあるメンバ変数だけを触る方法
クラス10~50個の中規模プログラミングに当たって,以下の問題がよく出てきて, 「簡潔な方法はないものか・・・」と悩んでいます. ●前提条件 【・「クラスAに持たれているクラスB( A have a B 関係)において, クラスBからクラスAのあるメンバ変数(プロパティ)だけを触りたい」】 この場合,私はよく以下のようにしてしまい,クラス間の独立性を無くしてしまいます. ●方法1: ・クラスBのコンストラクタに「自分の持ち主であるクラスAのオブジェクト」を引数に取り,クラスBの変数(プロパティ)usedClassAとする. ======= C言語風に書くと, <code> public void クラスA{ クラスB usingClassB = new クラスB(); // 持っているクラスB int commonNum = 0; public void AddCommonNum(){ commonNum++; } ... (その他の処理) ... } public void クラスB{ class usedClassA; // 持ち主のクラスA // コンストラクタ public クラスB(クラスA _usedClassA){ usedClassA = _usedClassA; } public void AddCommonNum(){ _usedClassA.AddCommonNum(); } } <\code> となります. この方法の問題点は,クラスAとクラスBに<双方向の依存関係を作っている>ことで, クラスAの設計(ここではAddCommonNum())が変更されたときに,クラスBの内容を変更しなければならない可能性があることから,拡張性に欠けると考えています. そこで,他に何かいい実装方法が無いか, 教えていただけないでしょうか? 特に,このような前提条件に汎用的に使える方法だと尚良いです.
- みんなの回答 (3)
- 専門家の回答
質問者が選んだベストアンサー
こんにちは。 簡単な話、共通のプロパティクラスを作成して、ClassAとClassBが其れを参照したり更新したりすれば、ClassAとClassBは左程依存しないのでは。 一応C++で、其れらしき事をしてみましたので、参考程度に。 #pragma warning(disable : 4786) #include<map> #include<string> //プロパティオブジェクトココから struct IVariantHolder { virtual ~IVariantHolder(){ } }; template<class __TP> struct CVariantHolder : public IVariantHolder { typedef typename __TP variant; explicit CVariantHolder(const variant& val) : m_val(val){ } ~CVariantHolder(){ } variant& Get(){ return m_val; } const variant& Get() const { return m_val; } operator variant&(){ return m_val; } operator const variant&() const { return m_val; } private: variant m_val; }; struct CPropertyData { typedef std::map<std::string, IVariantHolder*> map_t; template<class __TP> bool AddProperty(const __TP& tp, const std::string& sPropertyName) { if(GetProperty(sPropertyName))return false; return m_map.insert(std::make_pair(sPropertyName, new CVariantHolder<__TP>(tp))).second; } IVariantHolder* GetProperty(const std::string& sPropertyName) { return const_cast<IVariantHolder*>( static_cast<const CPropertyData&>(*this).GetProperty(sPropertyName) ); } const IVariantHolder* GetProperty(const std::string& sPropertyName) const { map_t::const_iterator it = m_map.find(sPropertyName); return it == m_map.end() ? 0 : it->second; } private: map_t m_map; }; template<class __TP> static __TP* Lock(CPropertyData* property, const std::string& sPropertyName) { CVariantHolder<__TP>* pVal = dynamic_cast<CVariantHolder<__TP>*>(property->GetProperty(sPropertyName)); return pVal ? &pVal->Get() : 0; } //プロパティオブジェクトココまで //テスト用の構造体 struct CommonStruct { CommonStruct(long _l, short _s, char _c) : l(_l), s(_s), c(_c){ } long l; short s; char c; }; //クラスAとクラスBココから struct ClassB; struct ClassA { ClassA(ClassB* p, CPropertyData* property) : m_useClassB(p), m_property(property) { //ココでメンバ変数(プロパティ)の領域を動的に作成する m_property->AddProperty(int(0), "commonNum"); m_property->AddProperty(CommonStruct(4, 2, 1), "commonStruct"); } void DisplayProperty() { ::printf("%s %d %s\n", "[commonNum : ", *::Lock<int>(m_property, "commonNum"), "]"); CommonStruct* p = ::Lock<CommonStruct>(m_property, "commonStruct"); ::printf("%s <%d><%d><%d> %s\n", "[commonStruct <l><s><c> : ", p->l, p->s, p->c, "]"); } private: ClassB* m_useClassB; CPropertyData* m_property; }; struct ClassB { explicit ClassB(CPropertyData* property) : m_property(property){ } void AddCommonNum() { int* pi = ::Lock<int>(m_property, "commonNum"); (*pi)++; } void SetCommonStruct(long l, short s, char c) { CommonStruct* pc = ::Lock<CommonStruct>(m_property, "commonStruct"); new (pc) CommonStruct(l, s, c); } private: CPropertyData* m_property; }; //クラスAとクラスBココまで //お試し int main() { //両方に共通なプロパティオブジェクト CPropertyData property; //クラスBに渡す ClassB b(&property); //クラスAにクラスBとプロパティオブジェクトを渡す ClassA a(&b, &property); //クラスBでプロパティを操作する b.AddCommonNum(); b.SetCommonStruct(10, 20, 30); //クラスAでプロパティを表示する a.DisplayProperty(); return 0; }
その他の回答 (2)
- machongola
- ベストアンサー率60% (434/720)
御礼・補足頂きました。 C++ではstructとclassの違いはアクセス指定のデフォルトが違うだけです。その他は全て一緒です。 structはデフォルトでpublic、classはデフォルトでprivateです。 私の書いたCPropertyDataクラスにデストラクタを書き忘れてしまいました・・・。 以下追記 ~CPropertyData() { for(map_t::iterator it = m_map.begin(); it != m_map.end(); ++it) delete it->second; }
そうだな、ちょっと具体例が思い浮かばないが、基本、やっぱりコンストラクタで引数を渡し保持するような形になるのはやむを得ないと思うな。 ただし、汎用性を考えるのであれば、Aをインターフェイスとして定義し、その実装としてAImplクラスを定義する。そしてクラスBでは、引数にAインターフェイスを渡すように考える。Aインターフェイスでは、B内から必要となるメソッドなりフィールドなりを実装するよう定義しておく。 であれば、AImplを変更したり別のクラスに差し替える必要が生じた場合も、新しいクラスにimplements Aするだけですむ。完全に依存性を排除することはできないが、弱い依存性でまとめることが可能になる。
お礼
お早いご回答ありがとうございます. インタフェースは,確かに良い方法だと思います. これをC言語風(と言っていましたが,すみません,C#です)に書くと, 以下のようになるかと思います. ※#region~#endregionは,C#用の折りたたみ可能コメントです. <code> #region クラスAのインタフェース public interface クラスAImpl{ private static int commonNum; // このインタフェースで1つの共通の値を格納したいのでstatic静的変数とする public static void AddCommonNum(); } #endregion public class クラスA : クラスAImpl{ #region クラスAインタフェースの実装(※クラスAImpl.をつけると,クラスAに他の同じ名前の定義があったときの衝突を防げる.) private static int クラスAImpl.commonNum = 0; public static void クラスAImpl.AddCommonNum(){ commonNum++; } #endregion ... (その他の処理) ... } #endregion #region クラスAインタフェースを使うクラスB public void クラスB{ クラスAImpl usedClassAImpl; public クラスB(クラスAImpl _classAImpl){ usedClassAImpl = _classAImpl; } public void AddCommonNum(){ usedClassAImpl.AddCommonNum(); } } #endregion #region テスト実行 public static void main(){ クラスA classA = new クラスA(); クラスB classB = new クラスB((クラスAImpl)classA); classA.AddCommonNum(); // クラスBでCommonNumを触る classB.AddCommonNum(); } #region しかし,この方法だと,やはりいくつか問題があると思います. ●方法3の問題 ・クラスBのコンストラクタにインタフェースAImplを指定しなければならない ・インタフェースの実装の手間 → 共通変数CommonNumを操作する関数AddCommonNum()などの振る舞いは,クラスに依存せず一定にしたいにも関わらず,実装の度に定義しなければなりません. もし,これより更に依存性を解除する方法がありましたら,教えていただけると幸いです.
補足
すみません,はじめの質問文と,このお礼に,プログラムの不備がありました. ●はじめの質問文の訂正 ※全てのクラスの定義箇所にvoidがありますが,正しくはclassです. 訂正箇所: × public void クラス → ○ public class クラス ●このお礼のプログラムの不備 ※このインタフェースはオブジェクトにより実装されているので,staticにする必要が無いです.また,staticなメソッドはオブジェクトから呼び出せません. 訂正箇所: 全てのstatic → (削除)
お礼
ご丁寧にコメント付きのソースを添付いただき,ありがとうございます. プロパティクラスについては,今回始めて知りました. 「// ここから~ここまでプロパティオブジェクト」あたりのソースは,私には理解が困難な部分もありましたが, CPropertyDataクラスの関数AddPropatyで共通変数を動的に作れることを生かして, クラスAとクラスBで使用する共通変数を,クラスAのコンストラクタで作成可能でありながら, クラスAとクラスBの依存性をゼロにしている,とても良い方法だと感じました. 概念がとても参考になりました. 補足で書いたC#のコードは,共通変数を動的に作れるようには実装できませんでした. C#で実装可能かはまだ勉強不足でよくわかりませんが, なんとかシンプルでわかりやすいものが出来ないか,頑張ってみます.
補足
自身の理解と確認のため,プロパティクラスの概念と,C#への実装方法を補足させていただきます. 変数commonNumの概念から考えると, まず,「変数commonNumがどこのクラスに属するか」を考えると, 本来,変数commonNumはクラスAとクラスBが使う共有のものと考えることが出来ます. つまり,クラスAとクラスBが,必ず同じ変数commonNumを持つことを過不足無く表した方法が最適かと考えます. そこで,変数commonNumを持つクラスPropertyを作り, そのオブジェクトを,クラスAとクラスBのコンストラクタ引数で参照することにより, クラスBからクラスAへの依存性をゼロにすることができます. これにより,残るクラス間依存は, ・クラスAからクラスB(クラスA has a クラスBなので当然といえば当然) ・クラスAからクラスProperty ・クラスBからクラスProperty に抑えられ, クラスBの仕様変更でクラスAを変更する可能性はゼロになります. # machongola様,この説明で間違っていれば補足いただけると幸いです. 例えば, これをC言語風(と言っていましたが,すみません,C#です)に書くと, <code> public class クラスProperty{ private int commonNum = 0; public void AddCommonNum(){ commonNum++; } } public class クラスA{ private クラスB classB { get; } ; // 持っているクラスBは公開プロパティで,クラス外部からclassBで取得できる private クラスProperty commonProperty; public void AddCommonNum(){ commonProperty.AddCommonNum(); } // 【実装1】コンストラクタで,クラスBを参照する場合 public クラスA(クラスB _classB, クラスProperty _commonProperty){ // 変数commonPropertyを参照する commonProperty = _commonProperty; // クラスBを参照する classB = _classB; } // 【実装2】もしくは,コンストラクタ内で新しいクラスBを生成する場合 public クラスA(クラスProperty _commonProperty){ commonProperty = _commonProperty; // クラスBも,同じ変数commonPropertyを参照する classB = new クラスB(_commmonProperty); } //... //(その他の処理) //... } public class クラスB{ private クラスProperty commonProperty; public void AddCommonNum(){ commonProperty.AddCommonNum(); } // コンストラクタ public クラスB(クラスProperty _commonProperty){ // 変数commonPropertyを参照する commonProperty = _commonProperty; } } // テスト実行 public static void main(){ // 両方に共通なプロパティオブジェクト クラスProperty commonProperty = new クラスProperty(); // 【実装1】クラスBを生成したクラスAを一行で生成するコンストラクタを使用した場合 クラスA classA = new クラスA(commonProperty); // commonProperty参照(同時に,クラスAのコンストラクタでクラスBも参照) // クラスBから変数commonNumを触る classA.classB.AddCommonNum(); // ここでの「classB.」はプロパティのgetで取得 // 【実装2】クラスBを生成してから,クラスAを生成するコンストラクタを使用した場合 クラスB otherClassB = new クラスB(commonProperty); クラスA otherClassA = new クラスA(classB, commonProperty); // クラスBから変数commonNumを触る otherClassB.AddCommonNum(); } <\code> となると思います. この方法は一般的によく使われるかわかりませんが,比較的シンプルで使いやすい方法だと考えられ,応用範囲が広いと感じました. もし不備や他に改善点などありましたら,ご教授いただければ嬉しいです. > machongola様 # またC++初心者の質問で申し訳ありませんが,C++で全てのクラスをstructで定義しているのは,どうしてですか? # 素人考えでは,何故struct構造体が関数(メソッド)を持てるのか不思議なのですが・・・(高速化のため?)