草庐IT

C++ 帮助重构一个怪物类

coder 2023-11-17 原文

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

等...

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

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

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

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

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

最佳答案

为了避免现在和将来滥用类继承关系,您实际上应该只在存在关系时才使用它。作为你自己,“cook_food 是一个 kitchen_worker”。它显然在现实生活中没有意义,在代码中也没有。 “cook_food”是一个 Action ,因此创建一个 Action 类并将其子类化可能是有意义的。

拥有一个新类只是为了添加新方法,如 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 的逻辑结论,但实际上违反了它 - 因为您正在创建负责切割的类,并保存一些关于什么的信息他们正在切割。切洋葱和胡萝卜都可以抽象为单个切割 Action - 您可以指定要切哪个对象,如果每个对象需要特定代码,还可以为每个单独的类添加重定向。

第一步是创建一个抽象来表示某些东西是可切割的。子类化的关系是候选关系,因为胡萝卜可切割的。

class cuttable {
    public:
        virtual void cut()=0;
}

class carrot : public cuttable {
    public:
      virtual void cut() {
          //specific code for cutting a carrot;
      }
}

切割 Action 可以采用可切割对象并执行适用于所有可切割对象的任何常见切割 Action ,也可以应用每个对象的特定切割行为。

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
        }
}

关于C++ 帮助重构一个怪物类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13171585/

有关C++ 帮助重构一个怪物类的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  9. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐