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

ここの料理人クラスはモンスタークラスらしく、モンスタークラスになる可能性を秘めている。シェフも単一責任の原則に違反しているように見えるため、代わりに次のようにする必要があります。

  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 オブジェクトの 3 つの異なるメソッドを確認して先に進む方が、非常に現実的な意味で簡単だと思います。

「cut_onions()」、「cut_carrots()」などのメソッドを追加すると、扱いにくくなる恐れがあることは理解しています...おそらくそれぞれに独自のデータがありますが、作成することで対処できるようですprep() 関数、たとえば、よりモジュール化されています。さらに、その論理的な結論に達した SRP はクラス「onion_cutters」「carrot_cutters」などを作成するように思われます...そして、何らかの形でプログラムが同じ従業員がタマネギとニンジンを切ります。これは、状態変数をメソッド間で同じに保つのに役立ちます (たとえば、従業員がタマネギを切る指を切ると、ニンジンを切る資格がなくなります)。一方、モンスター オブジェクトのシェフ クラスでは、すべてが処理されます。

もちろん、これが意味のある「オブジェクト指向設計」を持つことについて少なくなることは理解していますが、シェフのタスクごとに個別のオブジェクトを持たなければならない場合 (同じ人が3 つの機能をすべて実行する場合)、それは概念モデルよりもソフトウェア設計を優先するようです。オブジェクト指向の設計は、たとえば「meat_chef」、「sous_chef」、「three_star_chef」など、おそらく別の人が必要な場合に役立つと思います。さらに、ランタイムの問題に関連して、基本クラスの従業員を構成する基礎となるデータが変更され、この変更がその後の時間ステップに反映されます。

したがって、多かれ少なかれそのままにしておきたいという誘惑にかられます。誰かがなぜこれが悪い考えなのかを明確にすることができれば (そして、どのように進めるのが最善かについて提案があれば)、私は非常に感謝しています.

4

1 に答える 1

3

現在および将来のクラス階層の乱用を回避するには、実際には、is関係が存在する場合にのみ使用する必要があります。あなた自身として、「cook_foodは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();
            }
        }
    ...
}

これは、より一般的には戦略パターンとして知られています。既存のクラスを変更せずに新しいアクションを追加できるようにすることで、オープン/クローズの原則を促進します。


使用できる別のアプローチは、一連の抽象ステップを指定し、サブクラスを使用して各ステップの特定の動作を実装するテンプレートメソッドです。

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

これに適している可能性のあるもう1つの密接に関連するパターンは、ビルダーパターンです。

これらのパターンは、シーケンシャルカップリングのアンチパターンを減らすこともできます。これは、メソッドの呼び出しを忘れたり、正しい順序で呼び出すことを忘れがちであるためです。特に、複数回実行する場合はそうです。また、closes()が呼び出されることを心配する必要がないので、kitchen.opens()とcloses()を同様のテンプレートメソッドに入れることを検討することもできます。


onion_cutterとcarrot_cutterの個別のクラスを作成する場合、これは実際にはSRPの論理的な結論ではありませんが、実際には違反です。これは、カットを担当するクラスを作成し、それらが何であるかに関する情報を保持しているためです。切断。タマネギとニンジンの両方を1つの切断アクションに抽象化できます。また、切断するオブジェクトを指定し、オブジェクトごとに特定のコードが必要な場合は、個々のクラスにリダイレクトを追加できます。

1つのステップは、何かがカット可能であると言う抽象化を作成することです。ニンジンは切断可能であるため、サブクラス化の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 に答える