2017-03-17 124 views
2

以下面简化的C++类层次结构为例。我想要完成的是Service提供了一种虚拟方法来保存任意的对象。但是Service的每个子类,例如BoxService应该只能保存Box个对象。C++协变参数 - 设计模式

由于这样的事实:C++不支持方法参数的协方差,我不能简单地声明的保存方法BoxService.h,如:

void save(Box box); 

我的问题是,有没有什么最佳设计模式或最佳实践为问题?或者,如果到达的Model对象是Box类型,并且在其他情况下抛出异常,我应该检查BoxService.cpp中的保存函数的实现吗?

Model.h

class Model { 
private: 
    int id; 
}; 

Box.h

class Box : public Model { 
private: 
    int size; 
}; 

Service.h

class Service { 
public: 
    virtual void save(Model model); 
}; 

BoxSe rvice.h

class BoxService : public Service { 
public: 
    void save(Model box); 
}; 

BoxService.cpp

void BoxService::save(Model box) { 
    // TODO: save box and make sure that box is of type 'Box' and not any other subtype of 'Model' 
} 
+5

这打破了多态性。如果我得到一个'Service'并且我调用'save(model)',如果碰巧给了一个'BoxService',它就不会失败。逆变参数是另一种方式。如果'Service'可以保存一个'Box',那么'BoxService'可以将'Model'作为参数,因为通过'Service'的调用者将通过'Box'的Box's,但直接通过BoxService调用者'可以传递任何'Model'类型。这是C++不支持的。 – chris

+0

为什么'BoxService'继承'Service'?一袋苹果不是一袋水果。 – aschepler

+0

我同意!甚至可以在不破坏多态性的情况下设计一个类似于上述设计的设计? –

回答

1

所以,你喜欢你的声音要随机型集团化运作实现。我将解释更多的面向对象方法。

独立Service从实现,但我们要摆脱讨厌的参数:

class Service { ... }; 
class ServiceImpl { 
    virtual ~ServiceImpl() {} 
    void save() const = 0; 
    void remove() const = 0; 
}; 

每个实施将是轻量级和支持业务,但将采取参数构造函数:

class BoxService : public ServiceImpl { 
    Box _b; 

public: 
    BoxService(Box b) : _b(b) {} 

    void save() const { ... } 
    void remove() const { ... } 
}; 

现在我们有一个抽象工厂来创建实现,因为我们需要他们:

class ServiceImplFactory { 
public: 
    std::unique_ptr<ServiceImpl> create(Model m) const { 
     if (auto b = dynamic_cast<Box*>(m)) { 
      return std::make_unique<BoxService>(*b); 
     } else if (auto x = dynamic_cast<X*>(m)) { 
      return std::make_unique<XService>(*x); 
     } else { 
      assert(!"Not all Models covered by Service implementations"); 
     } 
    } 
}; 

现在Service

class Service { 
    ServiceImplFactory _implFactory; 

public: 
    void save(Model m) const { 
     return _implFactory.create(m)->save(); 
    } 

    void remove(Model m) const { 
     return _implFactory.create(m)->remove(); 
    } 
}; 

进一步的步骤:

  • 工程师的溶液,给出了一个编译时间错误,而不是断言的。
  • 允许添加更多的模型类型和更多的操作,而无需更改(很多)现有的代码。这应该等同于表达式问题。请注意,按照模型类型分组操作的这种方法需要进行更广泛的添加新操作的更改,而不是添加新的模型类型。相反,对于使用访问者和按操作对模型类型进行分组,这是正确的。有表达式问题的解决方案,例如object algebras,但它们可能有点模糊。
1

这里有可能是一个更实用的方法:

配对其实现每个模型类型:

template<typename T, typename ExtraType> 
struct WithType { 
    T value; 
    using extra_type = ExtraType; 

    WithType(T value) : value(value) {} 
}; 

定义Model作为一个变体,而不是继承层次:

using Model = std::variant<WithType<Box, BoxService>, WithType<X, XService>>; 

现在访问变种:

class Service { 
public: 
    void save(Model m) const { 
     visit([](auto withService) { 
      typename decltype(withService)::extra_type service; 
      service.save(withService.value); 
     }, m); 
    } 

    void remove(Model m) const { 
     visit([](auto withService) { 
      typename decltype(withService)::extra_type service; 
      service.remove(withService.value); 
     }, m); 
    } 
}; 
+0

差不多,但您没有BoxService的类型安全保证,在您的模型中可能包含BagService。 – lorro

+0

@lorro,我不明白。通过以这种方式定义'Model',您可以将正确的服务类型附加到模型对象。如果你调用'save(Box {})',它将使用'BoxService'。如果你调用'save(Bag {})',它会使用'BagService',假设'Model'的定义是正确的。如果你搞砸了,把'BagService'附加到'Box',我相信任何'visit'调用都会是一个编译器错误。 – chris

+0

你是对的,我误解了Model的def。 – lorro