7

我有 C 背景,是 C++ 的新手。我有一个基本的设计问题。我有一门课(我称它为“厨师”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();
     }

ETC...

诚然,我仍然在为如何在运行时实现它而苦苦挣扎,例如,如果盘子(或“以盘子身份的厨师”)决定在晚餐服务中途回家,那么厨师必须进行新的轮班.

这似乎与我有一个更广泛的问题有关,如果在这个例子中总是由同一个人进行准备、烹饪和摆盘,那么让这种层次结构来模拟单个厨师所做的真正实际优势是什么?我想这会遇到“害怕添加课程”的事情,但与此同时,现在或在可预见的将来,我不认为维护整个 chef 课程非常麻烦。我还认为,对于一个天真的代码读者来说,在真正意义上更容易看到 chef 对象中的三种不同方法并继续前进。

我知道当/如果我们添加诸如“cut_onions()”、“cut_carrots()”等方法时,它可能会变得笨拙,也许每个方法都有自己的数据,但似乎这些可以通过制作来处理prep() 函数,比如说,更加模块化。此外,似乎 SRP 得出其合乎逻辑的结论将创建一个类“onion_cutters”“carrot_cutters”等......我仍然很难看到它的价值,因为程序必须以某种方式确保同一员工切洋葱和胡萝卜,这有助于在不同方法中保持状态变量相同(例如,如果员工用手指切洋葱,他不再有资格切胡萝卜),而在怪物对象厨师类中,似乎所有这一切都得到了照顾。

当然,我理解这对于有意义的“面向对象设计”来说变得不那么重要了,但在我看来,如果我们必须为每个厨师的任务有单独的对象(这似乎不自然,因为同一个人是完成所有三个功能)然后似乎将软件设计优先于概念模型。我觉得面向对象的设计在这里很有帮助,如果我们想拥有可能是不同的人的“meat_chef”“sous_chef”“three_star_chef”。此外,与运行时问题相关的是,在严格应用单一职责原则的情况下,似乎存在复杂性开销,必须确保构成基类员工的底层数据发生变化,并且这种变化是反映在随后的时间步长中。

因此,我很想或多或少地保持原样。如果有人能澄清为什么这是一个坏主意(如果你有关于如何最好地进行的建议),我将非常感激。

4

1 回答 1

3

为了避免现在和将来滥用类层次结构,你应该只在存在is关系时才使用它。作为你自己,“cook_food 是一个厨房工人”。这显然在现实生活中没有意义,在代码中也没有。“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();
            }
        }
    ...
}

这通常被称为策略模式。它通过允许您在不修改现有类的情况下添加新操作来促进开放/封闭原则。


您可以使用的另一种方法是模板方法,您可以在其中指定一系列抽象步骤,并使用子类来实现每个步骤的特定行为。

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() { ... }
}

另一个可能适用于此的密切相关的模式是建造者模式

这些模式也可以减少顺序耦合反模式,因为很容易忘记调用某些方法,或者以正确的顺序调用它们,特别是如果你多次这样做。您还可以考虑将您的 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
        }
}

于 2012-11-01T07:07:59.000 回答