如何在聚合根类中编写只读访问器函数?

时间:2013-02-18 09:06:11

标签: c++ delegates domain-driven-design aggregateroot law-of-demeter

总体设计:我有一个汇总类C,其中包含NM_i, i = 1 ... N成员变量,每个变量都有公共只写 update()界面以及特定于类的只读访问者函数[F]un_i(), [F] = any letter, i = 1 .. N(实际上它们没有这样的常规名称)。每个成员类型M_i形成一个独立的抽象,并在我的程序的其他地方使用。

聚合类需要更新单个事务中的所有成员,因此它具有自己的update()函数,调用其所有成员变量的update()成员函数。

// 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_;
};

问题:我是否在聚合类中访问各种成员变量的各种成员函数?我可以想到三个替代方案

备选方案1 :将N * K个委托写入所有K成员变量的所有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 

备选方案2 :为所有N成员变量编写N个访问者

class C
{
    M_1 const& m1() const { return m1_; }

    // ...

    M_N const& mN() const { return mN_; }

    // as before
};

int res = C.m1().fun_5(); // call 5th member function of 1st member variable

备选方案3 :将1访问者模板写入所有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(); // call 5th member function of 1st member variable

备选方案1避免违反 Demeter法则,但它需要大量繁琐的样板代码(在我的应用程序N = 5K = 3中,所以{{1委托包装器)。备选方案2减少了包装器的数量,但是调用代码对我来说感觉有点丑陋。但由于所有代码都是只读的,并且修改只能通过一致的聚合15进行,我目前认为备选方案2优于备选方案1(并且至少是安全的)。如果是这种情况,那么更重要的是,备选方案3应该是最佳选择,因为它只使用一个访问器并且具有与备选方案2相同的安全保证。

问题:此类代码的首选界面是什么?

4 个答案:

答案 0 :(得分:1)

另一种可能性是

int func(int i, int j); // i,j can be enums as well..

虽然您需要决定这对您是否有意义。你需要在里面写一个巨大的嵌套开关,但界面更简单。

如果您可以将对象存储在数组中,并且所有成员函数都是M_i类型的公共接口的一部分,则此方法是理想的。

答案 1 :(得分:1)

为您提供具有编程时间分辨率的最佳用户友好代码的解决方案必须依赖于模板。

确实,如果您希望能够调用fun(i,j)(实际上fun<i,j>()),其中i是成员变量的索引,j是一个索引。这个变量的成员函数,那么你必须定义映射。两种映射。

  • 成员变量索引和变量本身之间的第一个映射,意味着成员变量索引和变量类型之间的映射。

  • 成员函数索引和成员函数本身之间的第二个映射。但是,由于此映射依赖于索引成员变量的类型,因此必须为每个组合定义它。如果不定义此映射,则无法为用户提供完全索引的解决方案。 或者反过来:如果你不希望调用者打扰第i个变量的类型,知道他想要调用的第j个函数的名称是什么(取决于第i个变量的类型),那么你必须提供映射。

这样,用户将能够在不知道第i个变量类型的情况下调用int v = c.fun<i, j>(),也不知道第i个变量的第j个函数的名称。

template <typename M, int K> int fun(M const & m) const;

template <> int fun<M_1, 1>(M_1 const & m) const { return m.fun_1(); }
template <> int fun<M_1, 2>(M_1 const & m) const { return m.fun_2(); }
template <> int fun<M_1, 3>(M_1 const & m) const { return m.fun_3(); }

template <> int fun<M_2, 1>(M_2 const & m) const { return m.fun_1(); }
template <> int fun<M_2, 2>(M_2 const & m) const { return m.fun_2(); }
template <> int fun<M_2, 3>(M_2 const & m) const { return m.fun_3(); }

...

class C
{
    // Define the specialized class type for every N
    template <int N> class Mi { typedef void M; };
    template <> class Mi<1> { typedef M_1 M; };
    template <> class Mi<2> { typedef M_2 M; };
    template <> class Mi<3> { typedef M_3 M; };

    // Define the function to get the member N
    template <int N> Mi<N>::M const & get_M() const;
    template <> Mi<1>::M const & get_M() { return m1; } const;
    template <> Mi<2>::M const & get_M() { return m2; } const;
    template <> Mi<3>::M const & get_M() { return m3; } const;

    // Define the member function to call member N, function K
    template <int N, int K>
    int fun() { return fun<Mi<N>::M, K>( get_M<N>(); }

};

现在,如果您希望用户可以使用ij作为运行时变量进行调用,那么这不是可行的方法。首选int fun(i, j)函数,其中包含大量ifswitch。你不能兼得。

答案 2 :(得分:1)

我会将更新行为与单个元素功能完全分开。所有M_i类都应该实现一个只包含更新方法的Updatable接口。

这允许您安全地将N个访问器公开给(非常量)可更新接口。

class Updatable{

public:
  virtual void update() = 0;
} ; 


class M_i : public Updatable{

public:
 void update();

};

鉴于聚合类C,您可以:

  • 将N访问器暴露给const M_i类

  • 请求给定M_i类的Updatable接口。通过访问此(非常量)引用,您可以安全地向任何M_i实例发出更新。

  • 直接调用代理更新。

class C{
    public:
              /** Returns the updatable interface related to M_1 */
              Updatable& getM_1Updater(){ return M_1}

              /** Returns the const reference to M_1*/
              const M_1& getM_1() const { return M_1}

              /** delegates update to each contained element */
              void update(){

                  m1.update();
                  m2.update();


                  [...]
              }

            };

答案 3 :(得分:1)

将我的评论转化为答案。

如果你决定选择替代1(N * K代表),你可以使用Boost.Preprocessor为你做样板工作:

#include <boost/preprocessor.hpp>

// Define identifier names

#define FUNCTIONS (fun)(gun)(hun)

#define MEMBER_NAMES (m1_)(m2_)(m3_)

#define SUFFIXES (_1)(_2)(_3)


// Utility "data structure"
// Used to hand down state from iteration over functions to iteration over suffixes

#define WRAP_DATA(function, member) \
  (2, (function, member))

#define UNWRAP_DATA_FUNTION(data) \
  BOOST_PP_ARRAY_ELEM(0, data)

#define UNWRAP_DATA_MEMBER(data) \
  BOOST_PP_ARRAY_ELEM(1, data)


// Accessor-generating functionality

  // Convenience macro for generating the correct accessor name
#define CREATE_FUNCTION_NAME(data, suffix) \
  BOOST_PP_CAT(UNWRAP_DATA_FUNCTION(data), suffix)

  // Macro generating one accessor delegation
#define GENERATE_ACCESSOR(r, data, suffix) \
  int CREATE_FUNCTION_NAME(data, suffix) () const { return UNWRAP_DATA_MEMBER(data).CREATE_FUNCTION_NAME(data, suffix) (); }


// Generate accessors

class C
{

  // Execute GENERATE_ACCESSOR once for each element of SUFFIXES
#define BOOST_PP_LOCAL_MACRO(iter) \
  BOOST_PP_SEQ_FOR_EACH(GENERATE_ACCESSOR, WRAP_DATA(BOOST_PP_SEQ_ELEM(iter, FUNCTIONS), BOOST_PP_SEQ_ELEM(iter, MEMBER_NAMES)), SUFFIXES)

#define BOOST_PP_LOCAL_LIMITS (0, BOOST_PP_SEQ_SIZE(FUNCTIONS) - 1)

  // Execute BOOST_PP_LOCAL_MACRO once for each value within BOOST_PP_LOCAL_LIMITS
#include BOOST_PP_LOCAL_ITERATE()

// rest of class C here
// ...

};

转换为伪代码以更好地突出工作逻辑:

FUNCTIONS = {fun, gun, hun};
MEMBER_NAMES = {m1_, m2_, m3_};
SUFFIXES = {_1, _2, _3};

struct Data {
  auto function, member;
};

auto createFunctionName(data, suffix) {
  return data.function + suffix;
}

auto generateAccessor(data, suffix) {
  return "int " + createFunctionName(data, suffix) + "() const { return " + data.member + "." + createFunctionName(data, suffix) + "(); }";
}


class C
{

for (i = 0; i < sizeof(FUNCTIONS); ++i) {
  foreach (suffix in SUFFIXES) {
    generateAccessor(Data(FUNCTIONS[i], MEMBER_NAMES[i]), suffix);
  }
}

};