设计一个更好的API接口,将结构从一个类传递到另一个类

q09*_*987 5 c++ api

我坚信以下设计理念:

1>应尽可能在存储数据的地方实施服务.

2> Getter和Setter是邪恶的,应该小心使用.

我宁愿不在这里争论两个论点,并假设他们有自己的优势.

这是我目前面临的挑战.我有两个类(即AComputerA),其中AComputer为A提供一些服务,A保存所有基本数据成员.

事实:由于系统设计,我不允许在AComputer内部组合A.我知道,它已经打破了我的观点1>计算应该与数据保持一致.

当从中传递数据AAComputer,我们必须传递10个(大约)单个参数,因此最好设计一个结构来执行此操作,否则构造函数列表将变得疯狂.存储的大多数数据AComputer都是存储的数据的直接副本A.我们选择将这些数据存储在内部,AComputer因为其他函数AComputer也需要这些变量.

这是一个问题(我要求考虑API维护和修改的最佳实践):

1>我们应该在哪里定义通道结构PassData

2>我们应该为struct提供getter/setter PassData吗?

我提供了一个示例代码,以便详细说明我的问题.我最好能找到一个真正的工作开源API来解决同一个问题,这样我就可以从中学习.

如果你看一下PassData m_data;课堂上的私人定义AComputer,我是故意这样做的.换句话说,如果我们改变底层实现AComputer,我们可以PassData m_data;用单个变量或其他东西替换但不要破坏接口PassData.所以在这个设计中,我没有为结构提供getter/setter PassData.

谢谢

class AComputer
{
public:
    struct PassData
    {   // int type just used as an illustration. Real data has different types,
        // such as double, data, string, enum, etc.
        // Note: they are not exact copies of variables from A but derived from them
        int m_v1;
        // from m_v1 to m_v10
        //...
        int m_v10;
    };

    // it is better to store the passed-in data since other functions also need it.
    AComputer(const PassData& pd) : m_data(pd) {}

    int GetCombinedValue() const
    { /* This function returns a value based the passed-in struct of pd */ }

private:
    PassData m_data;    
};

class A
{
private:
    int m_i1;
    // from m_i1 to m_i10
    // ...
    int m_i10;
    // from m_i11 to m_i20
    // ...
    int m_i20;

    boost::shared_ptr<AComputer> m_pAComputer;

public:
    A()
    {
        AComputer::PassData aData;
        // populate aData ...
        m_pAComputer = boost::shared_ptr<AComputer>(new AComputer(aData));
    }

    int GetCombinedValue() const
    {
        return m_pAComputer->GetCombinedValue();
    }
};
Run Code Online (Sandbox Code Playgroud)

Ale*_*zzi 11

我认为在开始之前最好澄清几点,你说:

如果你看私人PassData m_data; 在AC计算机中定义,我是故意这样做的.换句话说,如果我们改变了AComputer的底层实现,我们可以替换PassData m_data; 使用单个变量或其他东西,但不要破坏PassData的接口.

事实并非如此,PassData是您界面的一部分!您不能在不破坏客户端代码的情况下替换PassData,因为您需要在AComputer的构造函数中使用PassData.PassData不是实现细节,但它是纯接口.

第二点需要澄清:

2> Getter和Setter是邪恶的,应该小心使用.

正确!但是你应该知道POD(Plain-Old-Data结构)甚至是最差的.使用POD而不是使用getter和setter类的唯一优点是可以省去编写函数的麻烦.但真正的问题仍然是开放的,你班级的界面太麻烦,维护起来非常困难.

设计始终是不同要求之间的权衡:

虚假的灵活感

您的图书馆已经发布,很多代码都在使用您的课程.在这种情况下,PassData的变化将是戏剧性的.如果您可以在运行时支付一小笔费用,那么您可以灵活地使用界面.例如,AComputer的构造函数将是:

AComputer(const std::map<std::string,boost::any>& PassData);
Run Code Online (Sandbox Code Playgroud)

这里看一下boost :: any .您还可以为地图提供工厂,以帮助用户轻松创建地图.

  • 如果您不再需要字段,则代码不会更改.

缺点

  • 运行时间价格小.
  • 丢失编译器类型安全检查.
  • 如果您的功能需要另一个必填字段,您仍然有问题.客户端代码将编译但它将无法正常运行.

总的来说这个解决方案并不好,最后它只是原版的一个奇特版本.

战略模式

struct CalculateCombinedValueInterface
{
   int GetCombinedValue()=0;
   virtual ~CalculateCombinedValueInterface(){}
};

class CalculateCombinedValueFirst : CalculateCombinedValueInterface
{
   public:
       CalculateCombinedValueFirst(int first):first_(first){}
       int GetCombinedValue(); //your implementation here
   private:
       //I used one field but you get the idea
       int first_;
};
Run Code Online (Sandbox Code Playgroud)

客户端代码将是:

CalculateCombinedValueFirst* values = new CalculateCombinedValueFirst(42);

boost::shared_ptr<CalculateCombinedValueInterface> data(values);
Run Code Online (Sandbox Code Playgroud)

现在,如果要修改代码,则不应触及已部署的接口.面向对象的解决方案是提供一个从抽象类继承的新类.

class CalculateCombinedValueSecond : CalculateCombinedValueInterface
{
   public:
       CalculateCombinedValueFirst(int first,double second)
           :first_(first),second_(second){}
       int GetCombinedValue(); //your implementation here
   private:
       int first_;
       double second_;
};
Run Code Online (Sandbox Code Playgroud)

客户端将决定升级到新类还是保留现有版本.

  • 无需中断客户端代码即可改善界面.
  • 您没有触及现有代码,但在新文件中引入了新功能.
  • 如果需要更小的粒度控制,可能需要使用模板方法设计模式.

缺点

  • 使用虚函数的开销(基本上只有几皮秒!)
  • 你不能破坏现有的代码.您必须保持现有界面不变,并添加一个新类来模拟不同的行为.

参数数量

如果在一个函数中输入了一组十个参数,则这些值很可能与逻辑相关.您可以在课程中收集其中一些值.这些类可以组合在另一个类中,它将作为函数的输入.你在一个类中有10个(或更多!)数据成员的事实应该响铃.

单一职责原则说:

一个班级改变的理由绝不应该是一个原因.

这个原则的必然结果是:你的班级要小.如果你的班级有20个数据成员,你很可能会发现很多改变它的理由.

结论

在向客户端提供接口(任何类型的接口)之后,您无法对其进行更改(一个很好的示例是C++中编译器需要实现多年的所有弃用功能).请注意您提供的界面甚至是隐式界面.在您的示例中,PassData不是实现细节,但它是类接口的一部分.

参数数量是需要检查设计的信号.改变一个大班很难.你的类应该很小,只能通过接口(C++ slang中的抽象类)依赖于其他类.

如果你的班级是:

1)小而且只有一个原因需要改变

2)派生自抽象类

3)其他类使用指向抽象类的指针来引用它

您的代码可以轻松更改(但必须保留已提供的界面).

如果您不满足所有这些要求,您将遇到麻烦.

注意:要求2)和3)可以改变,如果不提供动态多态,设计使用静态多态.


Pet*_*hie 0

您可能会考虑重构以使用模式对象——该对象的唯一目的是包含方法调用的参数。有关更多详细信息:http://sourcemaking.com/refactoring/introduce-parameter-object