The overall design . I have an aggregate class C containing N member variables of type M_i, i = 1 ... N , each of which has a common record update() , as well as read-only access functions to the class [F]un_i(), [F] = any letter, i = 1 .. N (in reality they do not have such regular names). Each of the element types M_i forms an independent abstraction of its own and is used elsewhere in my program.
An aggregate class must update all members in a single transaction, so it has the update() function of its own call to the update() member function of all its member variables.
// building blocks M_i, i = 1 ... N class M_1 { public: // common write-only interface void update(); // M_1 specific read-only interface int fun_1() const; // ... int fun_K() const; private: // impl }; // ... class M_N { public: // common write-only interface void update(); // M_N specific read-only interface int gun_1() const; // ... int gun_K() const; private: // impl }; // aggregate containing member variables M_i, i = 1 ... N class C { public: // update all members in a single transaction void update() { m1_.update(); // ... mN_.update(); } // read-only interface?? see below private: M_1 m1_; // ... M_N mN_; };
Question : Do I need to access different member functions of different member variables in an aggregated class? I can come up with three alternatives :
Alternative 1 : write N * K delegate to all K member functions of all variables N
class C { int fun_1() const { return m1_.fun_1(); } // ... int fun_K() const { return m1_.fun_K(); } // ... int gun_1() const { return mN_.gun_1(); } // ... int gun_K() const { return mN_.gun_K(); } // as before }; int res = C.fun_5(); // call 5th member function of 1st member variable
Alternative 2 : write N accessors to all N member variables
class C { M_1 const& m1() const { return m1_; }
Alternative 3 : write 1 access pattern to all member variables N
class C { public: enum { m1, mN }; template<std::size_t I> auto get() const -> decltype(std::get<I>(data_)) { return std::get<I>(data_); } private: std::tuple<M_1, M_N> data_; }; int res = C.get<m1>().fun_5();
Alternative 1 does not allow violating the Law of Demeter , but this requires a lot of tedious boiler plate code (in my application N = 5 and K = 3 , so 15 delegation of wrappers). Alternative 2 reduces the number of wrappers, but the calling code seems a little ugly to me. But since all this code is read-only, and modfications can only happen through the serial update() aggregate, my current opinion is that alternative 2 is preferable to alternative 1 (and at least safe). If so, then all the more, alternative 3 should be the best choice, since it uses only one accessory and has the same security guarantees as alternative 2.
Question : What is the preferred interface for this type of code?