我有C语言背景,是C++上的新手。我有一个基本的设计问题。我有一个类(我称它为"chef“b/c我遇到的问题在复杂性和问题方面与此非常相似)基本上是这样工作的
class chef
{
public:
void prep();
void cook();
void plate();
private:
char name;
char dish_responsible_for;
int shift_working;
etc...
}
在伪代码中,这是按照如下方式实现的:
int main{
chef my_chef;
kitchen_class kitchen;
for (day=0; day < 365; day++)
{
kitchen.opens();
....
my_chef.prep();
my_chef.cook();
my_chef.plate();
....
kitchen.closes();
}
}
这里的厨师类似乎是一个怪物类,并且有可能成为一个怪物类。chef似乎也违反了单一责任原则,所以我们应该有这样的东西:
class employee
{
protected:
char name;
int shift_working;
}
class kitchen_worker : employee
{
protected:
dish_responsible_for;
}
class cook_food : kitchen_worker
{
public:
void cook();
etc...
}
class prep_food : kitchen_worker
{
public:
void prep();
etc...
}
和
class plater : kitchen_worker
{
public:
void plate();
}
等等。
诚然,我仍然在为如何在运行时实现它而苦苦挣扎,例如,如果plater (或“作为plater的厨师”)在晚餐服务中途决定回家,那么厨师必须工作一个新的班次。
这似乎与我有一个更广泛的问题有关,如果在这个例子中,准备、烹饪和电镀都是同一个人做的,那么让这个类的层次结构来模拟单个厨师所做的事情,真正的实际优势是什么?我猜这会遇到“害怕添加类”的问题,但同时,现在或可预见的未来,我不认为维护整个chef类是非常麻烦的。我还认为,对于一个天真的代码读者来说,在chef对象中看到三个不同的方法并继续下一步,在实际意义上更容易。
我知道当我们添加"cut_onions()“、"cut_carrots()”等方法时,它可能会变得笨拙,也许每个方法都有自己的数据,但似乎可以通过使prep()函数更加模块化来解决这些问题。此外,按照它的逻辑结论,SRP似乎会创建一个"onion_cutters“"carrot_cutters”等类……我仍然很难看到这一点的价值,因为程序必须以某种方式确保同一个员工切洋葱和胡萝卜,这有助于在方法之间保持状态变量相同(例如,如果员工切洋葱的手指被切了,他就不再有资格切胡萝卜),而在monster object chef类中,似乎所有这些都得到了处理。
当然,我理解这变得与有意义的“面向对象的设计”无关,但在我看来,如果我们必须为每个厨师的任务分别使用不同的对象(这似乎不自然,因为同一个人在做所有这三个功能),那么这似乎将软件设计置于概念模型之上。我觉得,如果我们想要"meat_chef“、"sous_chef”、"three_star_chef“这些不同的人,那么面向对象的设计在这里会很有帮助。此外,与运行时问题相关的是,在严格应用单一责任原则的情况下,似乎存在复杂性开销,必须确保组成基类employee的底层数据得到更改,并且这种更改反映在后续的时间步长中。
因此,我比较倾向于让它或多或少保持原样。如果有人能澄清为什么这是一个坏主意(如果你对如何更好地进行有建议),我将不胜感激。
发布于 2012-11-01 15:07:59
为了避免现在和将来滥用类层次结构,您应该只在存在 is 关系时才使用它。就像你自己一样,"is cook_food a kitchen_worker“。这显然在现实生活中没有意义,在代码中也没有意义。"cook_food“是一个操作,因此创建一个操作类并将其子类化可能是有意义的。
创建一个新类只是为了添加像cook()
和prep()
这样的新方法,这并不是对原来问题的真正改进-因为您所做的一切只是将方法包装在一个类中。你真正想要的是做一个抽象来做这些动作--所以回到动作类。
class action {
public:
virtual void perform_action()=0;
}
class cook_food : public action {
public:
virtual void perform_action() {
//do cooking;
}
}
然后,可以为厨师提供一个要按您指定的顺序执行的操作列表。例如,一个队列。
class chef {
...
perform_actions(queue<action>& actions) {
for (action &a : actions) {
a.perform_action();
}
}
...
}
这通常被称为Strategy Pattern。它促进了开放/关闭原则,允许您在不修改现有类的情况下添加新的操作。
您可以使用的另一种方法是Template Method,您可以在其中指定一系列抽象步骤,并使用子类来实现每个步骤的特定行为。
class dish_maker {
protected:
virtual void prep() = 0;
virtual void cook() = 0;
virtual void plate() = 0;
public:
void make_dish() {
prep();
cook();
plate();
}
}
class onion_soup_dish_maker : public dish_maker {
protected:
virtual void prep() { ... }
virtual void cook() { ... }
virtual void plate() { ... }
}
另一个密切相关的模式可能适用于此,它是Builder Pattern
这些模式还可以减少Sequential Coupling反模式,因为很容易忘记调用某些方法,或者以正确的顺序调用它们,特别是在多次调用的情况下。您还可以考虑将kitchen.opens()和closes()放入类似的模板方法中,这样就不必担心closes()会被调用。
关于为onion_cutter和carrot_cutter创建单独的类,这并不是SRP的逻辑结论,但实际上违反了它-因为您正在创建负责剪切的类,并保存有关它们正在剪切的内容的一些信息。切割洋葱和胡萝卜都可以抽象为一个单独的切割操作--您可以指定要切割的对象,如果需要每个对象的特定代码,还可以为每个单独的类添加重定向。
其中一步是创建一个抽象概念,说明某些东西是可切割的。子类化的 is 关系是候选的,因为胡萝卜是可切的。
class cuttable {
public:
virtual void cut()=0;
}
class carrot : public cuttable {
public:
virtual void cut() {
//specific code for cutting a carrot;
}
}
切割操作可以采用可切割对象并执行适用于所有可切割对象的任何通用切割操作,还可以应用每个对象的特定切割行为。
class cutting_action : public action {
private:
cuttable* object;
public:
cutting_action(cuttable* obj) : object(obj) { }
virtual void perform_action() {
//common cutting code
object->cut(); //specific cutting code
}
}
https://stackoverflow.com/questions/13171585
复制相似问题