在基于策略的设计中多次使用策略
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
/inbound
和roundtrip
,都可以使用合成)。
【参考方案1】:
您可以将数据成员添加到roundtrip
,而不是继承,但walk
和car
中的函数目前是protected
。如果您无法将walk
和car
中的功能公开,您可以:
与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<T>, helperS<S>
)
【讨论】:
@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
。您也可以将它们设为static
和public
,这样就不需要实例字段:
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;
//...
;
car
和 walk
派生自它。然后您的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
的是 A
s。这里肯定不是这样。不过,私有继承“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<class T> class roundtrip<T,T> /*...*/ ;
@KitsuneYMG:谢谢,但这里的特殊化非常冗长,而且 - 正如你所说 - 无法扩展。很高兴学习 type_traits 和 std::conditional (DyP)!
@the_ducky 看看 Boost.MPL。您应该能够编写一个可以执行此操作的通用算法,如果 Tn 相同,则将 Tn 替换为 T(n-1)。这只是比我愿意提出的关于 2 个基类的问题要多得多的工作,尤其是可变参数模板仅是 c++11 并且您的问题被标记为 [C++]以上是关于在基于策略的设计中多次使用策略的主要内容,如果未能解决你的问题,请参考以下文章