【问题标题】:Access to 'inner' classes in case of composition在组合的情况下访问“内部”类
【发布时间】:2014-07-17 07:49:32
【问题描述】:

我将某些功能封装在其他类中使用的类中。我觉得这叫作曲。

class DoesSomething01
{
    public:
        DoesSomething01();
        void functionality01();
        void functionality02();
};

class DoesSomething02
{
    public:
        DoesSomething02();
        void functionality01();
        void functionality02();
};

class ClassA
{
    public:
        ClassA();

    private:
        DoesSomething01 *m_doesSomething01;
        DoesSomething02 *m_doesSomething02;
};

如果我现在有一个“知道”ClassAClassB 并且必须使用/执行functionality01 和/或functionality02DoesSomething01 和/或DoesSomething02 我看到两种可能性:

a) 将这样的方法添加到ClassA 以提供ClassBDoesSomething01 和/或DoesSomething02 的直接访问:

DoesSomething01 *getDoesSomething01() { return *m_doesSomething01; }
DoesSomething02 *getDoesSomething02() { return *m_doesSomething02; }

ClassB 可以这样做:

m_classA->getDoesSomething01()->functionality01();

b) 将(在本例中为四个)方法添加到 ClassA,它将方法调用转发到 DoesSomething01DoesSomething02,如下所示:

void doesSomething01Functionality01() { m_doesSomething01->functionality01(); }
void doesSomething01Functionality02() { m_doesSomething01->functionality02(); }
void doesSomething02Functionality01() { m_doesSomething02->functionality01(); }
void doesSomething02Functionality02() { m_doesSomething02->functionality02(); }

哪个选项更好,为什么?

每个选项的优点/缺点是什么?

【问题讨论】:

  • getDoesSomthing01()getDoesSomthing02() 必须/应该返回一个指针:DoesSomthing01* getDoesSomthing01() { return m_doesSomthing01; }
  • 当然,你是对的。

标签: c++ design-patterns architecture composition


【解决方案1】:

第一个选项可以被认为是代码异味。根据 Robert C. Martin 的'Clean Code',它是“传递导航”,应该避免。引用作者:

一般来说,我们不希望单个模块对其了解太多 合作者。更具体地说,如果 A 与 B 合作,并且 B 与 C 协作,我们不希望使用 A 的模块了解 C。 (例如,我们不想要 a.getB().getC().doSomething();。)

第二个选项看起来更好。这是Facade 模式的经典用法。而且它更好,因为它隐藏了类DoesSomthing01DoesSomthing02 的其他功能。然后你就得到了它的简化视图,它比第一个选项更容易使用。

编辑:还有一件事。您有两个具有相同功能并由其他类聚合的类。你应该考虑在这里使用Stratey pattern。您的代码将如下所示:

class DoesSomething 
{
    public:
        virtual void functionality01() = 0;
        virtual void functionality02() = 0;
}

class DoesSomething01 : DoesSomething 
{
    public:
        DoesSomething01();
        void functionality01();
        void functionality02();
};

class DoesSomething02 : DoesSomething 
{
    public:
        DoesSomething02();
        void functionality01();
        void functionality02();
};

class ClassA
{
    public:
        ClassA();
       DoesSomething* doesSomething();                         // Getter
       void doesSomething(DoesSomething* newDoesSomething);    // Setter
       // ...

    private:
        DoesSomething *m_doesSomething;
};

那么你将只需要两个方法而不是四个:

void doesFunctionality01() { m_doesSomething->functionality01(); }
void doesFunctionality02() { m_doesSomething->functionality02(); }

【讨论】:

  • 关于传递导航:在不了解代码的具体用例的情况下,您也可以争辩说建议使用流畅的界面。
【解决方案2】:

第一种情况违反了得墨忒耳法则,该法则规定一个班级只能与其直接朋友交谈。基本上,第一种方法的问题是内部类 DoSomething01 和 DoSomething02 的任何更改都会触发 A 类和 B 类的更改,因为这两个类现在都直接依赖于这些内部类。

第二个选项更好,因为它从内部类中封装了 B 类,但这种解决方案的副作用是,现在 A 类有很多方法,除了委派给它的内部类之外,它们什么都做不了。这很好,但想象一下,如果 DoSomething01 有一个内部类 DoSomething03 并且 B 类需要在不直接知道它的情况下访问它的功能,那么 A 类将需要另一个方法来委托给 DoSomething01,然后再委托给 DoSomething03。在这种情况下,我认为最好让 B 类直接知道 DoSomething01 ,否则 A 类将有一个巨大的接口,只是简单地委托给它的内部类。

【讨论】:

    【解决方案3】:

    如果有许多类和/或许多方法要调用,那么发明是有意义的 抽象父类形式的接口:

    class SomeInterface
    {
        public:
        SomeInterface(){}
    
        virtual void functionally01() = 0;
        virtual void functionally02() = 0;
    }
    

    DoesSomthing01 和其他类会继承这个类:

    class DoesSomthing01 : public SomeInterface
    

    并实现方法。

    如果将键与此类的实例化相关联是有意义的
    您可以将这些对象存储在 ClassA 中,例如使用地图(这里我 使用整数作为键):

    class ClassA
    {
    private:
        std::map<int, SomeInterface*> m_Interfaces;
    
    public:
        SomeInterface* getInterface(const int key) 
        {
            std::map<int, SomeInterface*>::iterator it(m_Interfaces.find(key));
            if (it != m_Interfaces.end())
                return it->second;
            else
                return NULL;
        }
    };
    

    然后您可以从 ClassB 访问它们

    int somekey = ...;
    SomeInterface *myInter = m_classA->getInterface(somekey);
    if (myInter)
        myInter->functionally01();
    

    这样你只有一个独立的访问方法(getInterface()) 对象的数量。

    为了使用密钥对方法的访问进行编码,您可以 创建一个将键映射到闭包或简单 switch 语句的映射: 在 SomeInterface 中:

    public:
       void executeMethod(const int key)
       {
           switch(key)
           {
               case 1: functionally01(); break;
               case 2: functionally01(); break;
               default:
                   // error
       }
    
    
    int methodKey = ...;
    int objectKey = ...;
    SomeInterface *myInter = m_classA->getInterface(objectKey);
    if (myInter)
        myInter->executeMethod(methodKey);
    

    【讨论】:

      【解决方案4】:

      看起来是中介者模式的好案例。

      此模式管理他拥有的 2 个对象之间的通信。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-17
        • 1970-01-01
        • 1970-01-01
        • 2018-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多