在基于策略的设计中多次使用策略

Posted

技术标签:

【中文标题】在基于策略的设计中多次使用策略【英文标题】:multiple use of policies in policy-based design 【发布时间】:2013-11-14 16:06:44 【问题描述】:

我有一个类模板roundtrip,它采用两个策略。只要它们不同,一切都很好,但是使用一个策略两次会导致编译错误。

例子:

#include <iostream>

class walk 
protected:
    void move() 
        std::cout<<"i'm walking."<<std::endl;
    
;

class car 
protected:
    void move() 
        std::cout<<"i'm driving in a car."<<std::endl;
    
;

template<typename S, typename T>
class roundtrip : private S, private T 
public:
    void printSchedule(void) 
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        T::move();
    
;

int main(void)
    roundtrip<walk,car> LazyTrip;
    LazyTrip.printSchedule();

    roundtrip<car,car> VeryLazyTrip; // ERROR: error: duplicate base type ‘walk’ invalid
    VeryLazyTrip.printSchedule();

    return 0;

如何解决?还是有更好的设计来实现相同的行为?

编辑:我在策略中添加了包装器,用户界面没有改变。您如何看待这个解决方案,它的设计是否简洁?

template<typename T>
class outbound : private T 
protected:
    void moveOutbound(void) 
        T::move();
    
;

template<typename T>
class inbound : private T 
protected:
    void moveInbound(void) 
        T::move();
    
;

template<typename S, typename T>
class roundtrip : private outbound<S>, private inbound<T> 
public:
    void printSchedule(void) 
        std::cout<<"away: ";
        this->moveOutbound();

        std::cout<<"return: ";
        this->moveInbound();
    
;

【问题讨论】:

“它是一个干净的设计吗”可能是更适合CodeReview 的问题。也就是说,有些人认为你应该避免私有继承,除非你需要使用它(例如覆盖虚函数);见this SO question 和这个C++FAQ(outbound/inboundroundtrip,都可以使用合成)。 【参考方案1】:

您可以将数据成员添加到roundtrip,而不是继承,但walkcar 中的函数目前是protected。如果您无法将walkcar 中的功能公开,您可以:

roundtrip 模板成为朋友

class walk 
protected:
    void move() 
        std::cout<<"i'm walking."<<std::endl;
    
    template<class T, class S>
    friend class roundtrip;
;

通过帮助类使roundtrip 可以访问成员函数:

template<typename S, typename T>
class roundtrip 
private:
    struct helperT : private T
    
    public: // or private + friend roundtrip
        using T::move;
        using T::T;
     mT;

    struct helperS : private S
    
    public:
        using S::move;
        using S::S;
     mS;

public:
    void printSchedule(void) 
        std::cout<<"away: ";
        mT.move();

        std::cout<<"return: ";
        mS.move();
    
;

如果两种类型的接口相同,您可以使用帮助类模板而不是两个帮助类:

template<typename S, typename T>
class roundtrip 
private:
    template<class U>
    struct helper : private U
    
    public: // or private + friend roundtrip
        using U::move;
        using U::U;
    ;

    helper<S> mS;
    helper<T> mT;

public:
    void printSchedule(void) 
        std::cout<<"away: ";
        mT.move();

        std::cout<<"return: ";
        mS.move();
    
;

(也可以从辅助模板特化继承,例如class roundtrip : helperT&lt;T&gt;, helperS&lt;S&gt;

【讨论】:

@DyP 在不触及策略和用户界面的情况下解决了问题。你最后的想法,和我在 OP 中的 EDIT 例子一样吗? @the_ducky 是的;虽然我停止了继承的想法,并没有考虑转发具有不同名称的成员函数以避免歧义。【参考方案2】:

作为noticed by DyP,这就够了:

template<typename S, typename T>
class roundtrip 
    public:
    void printSchedule(void) 
        std::cout<<"away: ";
        awayPolicy.move();

        std::cout<<"return: ";
        returnPolicy.move();
    

    private:
    T awayPolicy;
    S returnPolicy;
;

但这需要使move 方法public。您也可以将它们设为staticpublic,这样就不需要实例字段:

class walk 
public:
    static void move() 
    std::cout<<"i'm walking."<<std::endl;
    
;

// the same for car

template<typename S, typename T>
class roundtrip 
    public:
    void printSchedule(void) 
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        T::move();
    
;

你也可以考虑创建一个基类:

class travelPolicy

public:
    virtual void move() = 0;        
    //...
;

carwalk 派生自它。然后您的roundtrip 类可以通过构造函数接受这两种策略,并通过指针在printSchedule 中使用它们。

class roundtrip 

public:
    roundtrip(
        std::shared_ptr<travelpolicy> awayPolicy,
        std::shared_ptr<travelpolicy> returnPolicy)
    
        this->awayPolicy = awayPolicy;
        this->returnPolicy = returnPolicy;
    


    void printSchedule(void) 
    
        std::cout<<"away: ";
        awayPolicy->move();

        std::cout<<"return: ";
        returnPolicy->move();
    

private:
    std::shared_ptr<travelPolicy> awayPolicy;
    std::shared_ptr<travelPolicy> returnPolicy;
;

UPDATE(响应 OP 的编辑):

您的解决方案总体上很好,但它似乎仍然过度使用继承。当A 继承B 时,可以说B As。这里肯定不是这样。不过,私有继承“hack”使这种尴尬不可见,这就是为什么它似乎可以接受的原因。

【讨论】:

私有继承被一些人认为更像是 is-implemented-in-terms-of 关系。例如,如果基类为空,它可能会很有用。 谢谢@BartoszKP,这些都是可行的解决方案。但是,我不希望更改策略,因为它们可能是由用户创建的,并且应该尽可能简单。 @the_ducky 没问题。你会发现 DyP 的回答更有帮助。 @DyP 和 BartoszKP :但是基于策略的设计总是违反 is-implemented-in-terms-of 关系,不是吗? @the_ducky 考虑我的最后一个例子 - 它以“OOP-正确”的方式使用继承,它被认为是基于策略的设计。【参考方案3】:

为什么不专门研究相同基类的情况呢? 如果可以的话,我会使用 boost 的 mpl::if_ 而不是我的和 c++11 或 boost 的 type_trait 的 is_same。 我手边没有编译器,所以下面可能有一些语法问题

#include <type_traits>

template<typename S, typename T>
class roundtrip_diff : private S, private T 
public:
    void printSchedule(void) 
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        T::move();
    
;

template<typename S>
class roundtrip_same : private S 
public:
    void printSchedule(void) 
        std::cout<<"away: ";
        S::move();

        std::cout<<"return: ";
        S::move();
    
;

template<bool, typename True, typename False> struct if_ typedef True type; ;

template<typename True, typename False> struct if_<false,True,False>  typedef False type; ;

template<typename A, typename B> struct is_same  enumvalue=0; ;
template<typename A> struct is_same<A,A>  enumvalue=1; ;

template<typename S, typename T>
class roundtrip : if_< is_same<S,T>::value, roundtrip_same<S>, roundtrip_diff<S,T> >::type  ;

显然,当您允许任意数量的参数时,需要找到更优雅的解决方案,但我认为这应该可以消除重复碱基的错误。

【讨论】:

@DyP 是的,除了上面没有 C++11 标记。 Why not specialize on the case of identical base classes? 这实际上听起来像是部分专业化的工作:template&lt;class T&gt; class roundtrip&lt;T,T&gt; /*...*/ ; @KitsuneYMG:谢谢,但这里的特殊化非常冗长,而且 - 正如你所说 - 无法扩展。很高兴学习 type_traits 和 std::conditional (DyP)! @the_ducky 看看 Boost.MPL。您应该能够编写一个可以执行此操作的通用算法,如果 Tn 相同,则将 Tn 替换为 T(n-1)。这只是比我愿意提出的关于 2 个基类的问题要多得多的工作,尤其是可变参数模板仅是 c++11 并且您的问题被标记为 [C++]

以上是关于在基于策略的设计中多次使用策略的主要内容,如果未能解决你的问题,请参考以下文章

Rails 5 和设计:如何在不更改默认策略的情况下禁用基于令牌的策略上的会话

是否可以将基于策略的设计与自动化测试一起使用?

基于策略的设计——策略实现必须访问宿主类的成员

23种设计模式之策略设计模式

PHP设计模式——策略模式

C++ 中 QObject 多重继承和策略/特征设计的问题