如何使用容器中对象的继承层次结构来维护开放/封闭原则

How to maintain the open/closed principle with inheritance hierarchies of objects in containers?

本文关键字:维护 原则 层次结构 何使用 对象 继承      更新时间:2023-10-16

当我想向这个结构添加功能而不改变它时:

class MemberBase
{
public:
    virtual MemberBase* clone() const; 
    virtual void action1(); 
    ... 
}
class Container
{
public:
    virtual void functionUsingAction1();
    ...
private:
    std::vector<MemberBase*> members_
}

我可以子类成员库和

class MemberType1 : public MemberBase
{
public:
    MemberBase* clone() const;// Reimplementation
    void action1();           // Reimplementation
    virtual void action2();   // New functionality
}

我可以通过使用

MemberBase* MemberBase::clone() const;

方法。但是我该怎么打电话

void MemberType1::action2(); 

从容器类的方法?我可以使用dynamic_cast,但这被认为是设计缺陷。有什么解决方法吗?或者更好的是,我可以遵循任何模式?

老实说,我看得越多,对我来说它看起来像一个典型的访问者应用程序。

我建议避免在Container类中实现任何智能,而是将操作委托给特定的Visitor类。可以为基础Visitor提供某种方式来安全地与底层容器结构进行交互(擦除/插入)。

// MemberBase.hpp
class Visitor;
class MemberBase {
public:
  virtual MemberBase* clone() const = 0;
  virtual void accept(Visitor&) = 0;
  virtual void accept(Visitor&) const = 0;
}; // class MemberBase
// Visitor.hpp
class Member1;
class Member2;
class Visitor {
public:
  virtual void visit(Member1&) = 0;
  virtual void visit(Member1 const&) = 0;
  virtual void visit(Member2&) = 0;
  virtual void visit(Member2 const&) = 0;
};
// Container.hpp
#include <MemberBase.hpp>
class Container {
public:
  void accept(Visitor& v) {
    BOOST_FOREACH(MemberBase& mb, _members) {
      mb.accept(v);
    }
  }
  void accept(Visitor& v) const {
    BOOST_FOREACH(MemberBase const& mb, _members) {
      mb.accept(v);
    }
  }
private:
  boost::ptr_vector<MemberBase> _members;
};

然后,您需要实现成员的accept方法。这纯粹是机械的。

// Member1.hpp
#include <MemberBase.hpp>
class Member1: public MemberBase {
public:
  virtual Member1* clone() const { return new Member1(*this); }
  virtual void accept(Visitor& v);
  virtual void accept(Visitor& v) const;
};
// Member1.cpp
#include <Member1.hpp>
#include <Visitor.hpp>
void Member1::accept(Visitor& v) { v.visit(*this); }
void Member1::accept(Visitor& v) const { v.visit(*this); }

最后,您可以实现一个访问者:

// CountVisitor.hpp
#include <Visitor.hpp>
class CountVisitor: public Visitor {
public:
  CountVisitor(): _count(0) {}
  size_t count() const { return _count; }
  virtual void visit(Member1&);
  virtual void visit(Member1 const&);
  virtual void visit(Member2&);
  virtual void visit(Member2 const&);
private:
  size_t _count;
};
// CountVisitor.cpp
#include <CountVisitor.hpp>
//#include <Member1.hpp> // where you would include, but unnecessary here
void CountVisitor::visit(Member1&) { ++_count; }
void CountVisitor::visit(Member1 const&) { ++_count; }
void CountVisitor::visit(Member2&) { ++_count; }
void CountVisitor::visit(Member2 const&) { ++_count; }

您可以将其用作:

// main.cpp
#include <iostream>
#include <Container.hpp>
#include <CountVisitor.hpp>
int main() {
  Container const c = /* something */;
  CountVisitor cv;
  c.accept(cv);
  std::cout << cv.count() << " items in the containern";
}

这种设计的缺点是需要为每个直接从MemberBase派生的新类实现一个新的visit方法,因此MemberBase层次结构不是那么开放。这可以使用非循环访客来缓解;但是,我很少需要它。

为了"扩展"能力,你可以让Visitor::visitMemberBase::accept返回要执行的"操作"(擦除、克隆等),并在你实际拥有的两个循环中处理这个问题。可以使用一些技巧将其减少到一个循环...

如果你知道每个成员会发布不同的函数,你可以总是创建像EnumerateActions(),CallAction(std::string action)等方法——这会更安全,但可以比作用火箭筒射击麻雀。

另一种选择是创建接口:IAction1,IAction2并在成员派生类中实现它们。然后,您只需检查成员是否实现接口并使用它。容器不会识别特定的成员类,但您仍然可以访问其特定功能。

我能想到的一个选项 o 检查每个对象是否是 MemberType1 对象,如果条件为真,那么您可以调用 action2()。这是一个非常幼稚的解决方案,但我相信其他人很快就会想出一个聪明的解决方案:)