【问题标题】:Strategy pattern vs Inheritance策略模式与继承
【发布时间】:2018-07-02 01:09:09
【问题描述】:

我们有一些算法可以应用于某些数据,并且该算法可能会多次应用于相同的数据。我们有两种方法可以做到这一点:

  1. 保持数据和逻辑分开

    class Algo{
    public:
        virtual int execute(data_object) = 0;
    };
    
    class AlgoX: public Algo{
    public:
        int execute(data_object);
    };
    
    class AlgoY: public Algo{
    public:
        int execute(data_object);
    };
    
    class Data{
    public:
        string some_values;
        ...
        void* algo_specific_data; //It will contain some algo specific data (like state of algo)
        Algo* algo_ptr; //Reference of Algo
    
        int execute(){
            algo_ptr->execute(this);
        }
    };
    
    some_function(){
        Data* data_object = create_data(algo_ptr, algo_specific_data); //A dummy function which creates an object of type data.
        data_object->execute();
    }
    
  2. 通过继承绑定数据和逻辑

    class Data{
    public:
        string some_values;
        ...
        virtual int execute() = 0;
    };
    
    class DataWithAlgoX : public Data{
    public:
        AlgoX_Relateddata algo_related_data; //some algo specific data (like state of algo)
        int execute();
    }
    
    class DataWithAlgoY : public Data{
    public:
        AlgoY_Relateddata algo_related_data; //some algo specific data (like state of algo)
        int execute();
    }
    
    some_function(){
        Data* data_object = create_data(algo_type); //A dummy function which creates an object of type data.
        data_object->execute();
    }
    

哪种设计更好,如果

  1. 我们可以在algo->execute()对数据的多次调用之间改变算法类型 (但切换不会很频繁,只有在某些特定场景下才需要)。
    有人可能会指出,切换算法会使我们重新创建data_object。 如果架构 21 好得多,我们愿意承担额外的负担。

  2. 我们不会在algo->execute() 对数据的多次调用之间更改算法类型。

【问题讨论】:

  • 在选项 1 中,您可以考虑使用 std::function 而不是类。
  • @Germán 为简单起见,我在Algo 中只使用了一个函数execute()。其中可能有多个私有函数,它们将被execute() 调用。
  • 该函数调用其他函数没有障碍;-) 无论如何,这只是一个需要考虑的想法。只有您知道真正问题的细节,例如算法与数据的关联程度,或者保持任何状态的必要性。
  • 没有回答您的问题,但您考虑过使用模板吗?
  • @MikeMB 你能否分享更多关于模板的想法......或者你能写一个描述你正在谈论的方式的答案......这里也可以接受其他有效的建议......

标签: c++ oop inheritance design-patterns strategy-pattern


【解决方案1】:

在(非常)糟糕的做法中将数据和算法混合在同一个班级中。 In 打破了单一责任原则。

https://en.wikipedia.org/wiki/Single_responsibility_principle

如果您想将多种类型的数据与多种算法相结合 使用类似调解器的东西。这个想法是分别定义数据和算法,并在中介中定义它们之间的交互。

https://en.wikipedia.org/wiki/Mediator_pattern

在我看来,设计 2 比设计 1 差得多。即使在设计 1 的情况下,我也会删除对 Data 类中算法的引用。它只引入了高耦合,即。 e.一个类之间的依赖关系,它影响了另一个类的变化:

https://en.wikipedia.org/wiki/Coupling_(computer_programming)

(和google“低耦合,高内聚”,是另一个OOP原则)。

Mediator 也可以解决耦合问题。

【讨论】:

    【解决方案2】:

    我希望始终将 Algo 与 Data 分开。一般来说,相同的数据可以用于不同的算法,相同的算法可以用于不同的数据。因此,如果您将其作为继承来实现,那么它会导致代码重复或子类的组合爆炸,例如DataWithAgloADataWithAlgoB

    更重要的是数据提供者,即生成数据的系统可能不需要知道要在那里使用的复杂算法。生成数据可能是一个非常愚蠢的系统,并且可能有研究人员正在更新算法。保留数据和算法本质上违反了Single Responsible Principle。现在你的 DataWithAlgo 类有 2 个来自 Algo 和来自 Data 的变化轴(正如鲍勃叔叔所说)。

    保持数据和算法分开保持代码灵活和易于更改,也满足SRP。这减少了代码中的耦合,避免了任何组合爆炸。所以我总是会把 Algo 从 Data 中分离出来。

    【讨论】:

      【解决方案3】:

      策略模式与继承

      在这两者之间,更喜欢前者而不是后者。在继承中,您不仅继承了 API 契约,还继承了 behavior,这可能会也可能不会被覆盖。在您的情况下,由于您声明多个算法可能会应用于同一类今天但不一定明天,因此以这种方式应用继承将导致类和如您所示,重复代码。

      然而,

      我们有两种方法可以做到这一点

      怎么会?你考虑过Decorator Pattern(我最喜欢的)吗?根据您的数据节点的结构,甚至Visitor Pattern 也是一个有效的选择。

      我还警告不要笼统地提出“混合数据和算法总是会破坏SRP”的建议。您仅在实际出现用例时才引入“2 轴变化”。理想情况下,如果您有完美的封装,那么类型就没有理由不能处理并将算法应用于自己的数据。这取决于域;在您的情况下,这显然不(似乎?)适用。

      【讨论】:

        猜你喜欢
        • 2019-01-06
        • 2014-10-31
        • 2011-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-04
        • 2011-12-02
        相关资源
        最近更新 更多