访问父类对象集合成员的子类函数

Eni*_*man 0 c++ oop inheritance design-patterns

(有关问题的简明版本,请参阅更新#1.)

我们有一个名为(抽象)类Games有子类,说BasketBallHockey(可能还有更多的后来来了).

另一个类GameSchedule,必须包含GamesCollection各种Games对象的集合.问题在于,我们有时只想迭代BasketBall对象GamesCollection和调用特定于它的函数(并且在Games类中没有提到).

也就是说,GameSchedule处理大量属于Games类的对象,因为它们确实具有被访问的共同功能; 同时,还有更多的粒度来处理它们.

我们愿拿出避免不安全的向下转换设计,并且是在这个意义上扩展,创造在许多子类Games或任何其现有的子类必须不必需添加太多的代码来处理这一要求.

例子:

  • 我提出的一个笨拙的解决方案,根本不做任何向下转换,就是在Game类中为每个必须调用的子类特定函数设置虚函数GameSchedule.这些虚函数将在适当的子类中具有覆盖实现,这实际上需要它的实现.

  • 我们可以为各种子类Games而不是单个容器显式维护不同的容器.但是GameSchedule当子类数量增加时,这将需要大量额外的代码.特别是如果我们需要迭代所有Games对象.

这样做有一个简洁的方法吗?

注意:代码是用C++编写的

更新#1:我意识到可以用更简单的方式提出问题.是否可以为属于类层次结构的任何对象创建容器类?此外,此容器类必须能够从层次结构中选择属于(或派生自)特定类的元素并返回适当的列表.

另外,在上述问题的背景下,容器类必须具有类似功能GetCricketGames,GetTestCricketGames,GetBaseballGame等,

Lil*_*ste 5

这正是"告诉,不要问"原则的问题之一.

您正在描述一个对象,该对象保留对其他对象的引用,并且在告诉他们需要做什么之前想要询问它们是什么类型的对象.从上面链接的文章:

问题在于,作为调用者,您不应该根据被调用对象的状态做出决定,这会导致您更改对象的状态.您实现的逻辑可能是被调用对象的责任,而不是您的责任.对于你在对象之外做出决定违反了它的封装.

如果您违反封装规则,您不仅会引入猖獗的向下转发所带来的运行时风险,而且还会使组件更容易紧密耦合,从而使系统的可维护性显着降低.


现在就在那里,让我们来看看"告诉,不要问"如何应用于您的设计问题.

让我们来看看你陈述的约束(没有特别的顺序):

  1. GameSchedule 需要迭代所有游戏,执行一般操作
  2. GameSchedule需要迭代所有游戏的子集(例如Basketball),以执行特定于类型的操作
  3. 没有垂头丧气
  4. 必须轻松容纳新的Game子类

遵循"告诉,不要问"原则的第一步是确定将在系统中发生的操作.这让我们退后一步,评估系统应该做什么,而不会陷入应该如何做的细节.

你在@MarkB的答案中做了如下评论:

如果有一个TestCricket继承自的类Cricket,并且它有许多关于匹配的各个局的时间的特定属性,并且我们想要将所有TestCricket对象的时序属性的值初始化为某个预设值,我需要一个选择所有的循环TestCricket对象和调用一些函数setInningTimings(int inning_index, Time_Object t)

在这种情况下,动作是:"将所有TestCricket游戏的局中定时初始化为预设值."

这是有问题的,因为想要执行此初始化的代码无法区分TestCricket游戏和其他游戏(例如Basketball).但也许它不需要......

大多数游戏都有一些时间因素:篮球比赛有时间限制,而棒球比赛基本上无限时间(基本上).每种类型的游戏都可以拥有自己完全独特的配置.这不是我们想要卸载到单个类东西.

而不是询问每个游戏的类型Game,然后告诉它如何初始化,考虑如果GameSchedule简单地告诉每个Game对象初始化,事情将如何工作.这将初始化的责任委托给了Game- 类的子类,它实际上知道它是什么类型的游戏.

起初这可能会让人觉得很奇怪,因为GameSchedule对象正在放弃对另一个对象的控制.这是好莱坞原则的一个例子.与大多数开发人员最初学习的方法相比,这是解决问题的完全不同的方式.

该方法通过以下方式处理约束:

  1. GameSchedule可以Game毫无问题地遍历s 列表
  2. GameSchedule不再需要知道它的亚型Game小号
  3. 不需要向下转换,因为子类本身正在处理特定于子类的逻辑
  4. 当添加新的子类时,不需要在任何地方更改逻辑 - 子类本身实现必要的细节(例如,InitializeTiming()方法).

编辑:这是一个例子,作为一个概念验证.

struct Game
{
    std::string m_name;

    Game(std::string name)
        : m_name(name)
    {
    }

    virtual void Start() = 0;
    virtual void InitializeTiming() = 0;
};

// A class to demonstrate a collaborating object
struct PeriodLengthProvider
{
    int GetPeriodLength();
}

struct Basketball : Game
{
    int m_period_length;
    PeriodLengthProvider* m_period_length_provider;

    Basketball(PeriodLengthProvider* period_length_provider)
        : Game("Basketball")
        , m_period_length_provider(period_length_provider)
    {
    }

    void Start() override;

    void InitializeTiming() override
    {
        m_period_length = m_time_provider->GetPeriodLength();
    }
};

struct Baseball : Game
{
    int m_number_of_innings;

    Baseball() : Game("Baseball") { }

    void Start() override;

    void InitializeTiming() override
    {
        m_number_of_innings = 9;
    }
}


struct GameSchedule
{
    std::vector<Game*> m_games;

    GameSchedule(std::vector<Game*> games)
        : m_games(games)
    {
    }

    void StartGames()
    {
        for(auto& game : m_games)
        {
           game->InitializeTiming();
           game->Start();
        }
    }
};
Run Code Online (Sandbox Code Playgroud)