Eni*_*man 0 c++ oop inheritance design-patterns
(有关问题的简明版本,请参阅更新#1.)
我们有一个名为(抽象)类Games
有子类,说BasketBall
和Hockey
(可能还有更多的后来来了).
另一个类GameSchedule
,必须包含GamesCollection
各种Games
对象的集合.问题在于,我们有时只想迭代BasketBall
对象GamesCollection
和调用特定于它的函数(并且在Games
类中没有提到).
也就是说,GameSchedule
处理大量属于Games
类的对象,因为它们确实具有被访问的共同功能; 同时,还有更多的粒度来处理它们.
我们愿拿出避免不安全的向下转换设计,并且是在这个意义上扩展,创造在许多子类Games
或任何其现有的子类必须不必需添加太多的代码来处理这一要求.
例子:
我提出的一个笨拙的解决方案,根本不做任何向下转换,就是在Game
类中为每个必须调用的子类特定函数设置虚函数GameSchedule
.这些虚函数将在适当的子类中具有覆盖实现,这实际上需要它的实现.
我们可以为各种子类Games
而不是单个容器显式维护不同的容器.但是GameSchedule
当子类数量增加时,这将需要大量额外的代码.特别是如果我们需要迭代所有Games
对象.
这样做有一个简洁的方法吗?
注意:代码是用C++编写的
更新#1:我意识到可以用更简单的方式提出问题.是否可以为属于类层次结构的任何对象创建容器类?此外,此容器类必须能够从层次结构中选择属于(或派生自)特定类的元素并返回适当的列表.
另外,在上述问题的背景下,容器类必须具有类似功能GetCricketGames
,GetTestCricketGames
,GetBaseballGame
等,
这正是"告诉,不要问"原则的问题之一.
您正在描述一个对象,该对象保留对其他对象的引用,并且在告诉他们需要做什么之前想要询问它们是什么类型的对象.从上面链接的文章:
问题在于,作为调用者,您不应该根据被调用对象的状态做出决定,这会导致您更改对象的状态.您实现的逻辑可能是被调用对象的责任,而不是您的责任.对于你在对象之外做出决定违反了它的封装.
如果您违反封装规则,您不仅会引入猖獗的向下转发所带来的运行时风险,而且还会使组件更容易紧密耦合,从而使系统的可维护性显着降低.
现在就在那里,让我们来看看"告诉,不要问"如何应用于您的设计问题.
让我们来看看你陈述的约束(没有特别的顺序):
GameSchedule
需要迭代所有游戏,执行一般操作GameSchedule
需要迭代所有游戏的子集(例如Basketball
),以执行特定于类型的操作Game
子类遵循"告诉,不要问"原则的第一步是确定将在系统中发生的操作.这让我们退后一步,评估系统应该做什么,而不会陷入应该如何做的细节.
你在@MarkB的答案中做了如下评论:
如果有一个
TestCricket
继承自的类Cricket
,并且它有许多关于匹配的各个局的时间的特定属性,并且我们想要将所有TestCricket
对象的时序属性的值初始化为某个预设值,我需要一个选择所有的循环TestCricket
对象和调用一些函数setInningTimings(int inning_index, Time_Object t)
在这种情况下,动作是:"将所有TestCricket
游戏的局中定时初始化为预设值."
这是有问题的,因为想要执行此初始化的代码无法区分TestCricket
游戏和其他游戏(例如Basketball
).但也许它不需要......
大多数游戏都有一些时间因素:篮球比赛有时间限制,而棒球比赛基本上无限时间(基本上).每种类型的游戏都可以拥有自己完全独特的配置.这不是我们想要卸载到单个类上的东西.
而不是询问每个游戏的类型Game
,然后告诉它如何初始化,考虑如果GameSchedule
简单地告诉每个Game
对象初始化,事情将如何工作.这将初始化的责任委托给了Game
- 类的子类,它实际上知道它是什么类型的游戏.
起初这可能会让人觉得很奇怪,因为GameSchedule
对象正在放弃对另一个对象的控制.这是好莱坞原则的一个例子.与大多数开发人员最初学习的方法相比,这是解决问题的完全不同的方式.
该方法通过以下方式处理约束:
GameSchedule
可以Game
毫无问题地遍历s 列表GameSchedule
不再需要知道它的亚型Game
小号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)