【问题标题】:C++ Help on refactoring a monster class重构怪物类的 C++ 帮助
【发布时间】:2012-11-01 05:42:24
【问题描述】:

我有 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();
     }

等等……

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

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

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

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

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

【问题讨论】:

  • 有时将现实世界的角色/职责/任务映射到代码中的对象是行不通的。也许你需要一些通用的函数来接受一个人和一个动作。该函数让用户应用该操作。
  • 每个类接口上的模块可能会给你更多的线索?像电镀工能做什么? cook_food 能做什么?他们需要继承还是只是一种技能(函数调用)?
  • 看合成方法。或者你可能需要一个状态模式?
  • 非常感谢大家的建议,他们给了我很好的起点!

标签: c++ refactoring anti-patterns oop


【解决方案1】:

为了避免现在和将来滥用类层次结构,您应该只在存在 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();
            }
        }
    ...
}

这通常称为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
        }
}

【讨论】:

  • 谢谢。我打算用一个问题来跟进它。因此,如果我们遵循策略模式方法,假设每个厨师都有许多数据成员(例如,salt_spoon、salt_shaker 等)在 cook_food() 和 plate_dish() 类中使用。似乎类厨师需要 cook_food() 和 cook_food() 需要类厨师。如何避免说 cook_food() 和类 chef 之间的循环依赖,而不必为 cook_food() 类提供大量参数?从设计的角度来看,如果类是高度耦合的,那么分离它们不是有缺点吗?
  • 避免循环依赖的一种方法是创建一个厨师接口,cook_food 类可以使用该接口,并且实际的厨师类可以实现。只要接口只指定cook_food 了解厨师所需的详细信息,就没有问题。 (或者你可以反过来做,制作一个烹饪界面供厨师食用。)没有唯一正确的方法,但将每个任务分成自己的类肯定是​​有好处的。
  • 抱歉,类能够使用接口是什么意思?这是否意味着做类似的事情,如果我们使用厨师类的接口,例如,cook_food(chef->interface_for_cooking()),interface_for_cooking() 由 get_salt_shaker()、get_tasting_spoon() 等调用组成。 .?以这个例子为例,为 cook_food() 和 plate_dish() 类设置两个单独的接口是不是一个坏主意?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-29
  • 2010-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-05
  • 2011-10-27
相关资源
最近更新 更多