27

複数のスレッド/スタックオーバーフローのコメントで、使用switchが悪いOOPスタイルであると書かれているのを見ました。個人的にはこれには反対です。

enumスイッチをオンにしたいクラスにコード (つまりメソッド) を追加できない場合が多くあります。これは、クラスを制御していないためです。おそらくサードパーティの jar ファイルに含まれているためです。enum 自体に機能を配置することは、懸念事項の分離に関する考慮事項に違反するため、または実際には enumeration と同様に他の何かの関数であるため、悪い考えである他のケースがあります。

最後に、スイッチは簡潔で明確です。

boolean investable;
switch (customer.getCategory()) {
    case SUB_PRIME:
    case MID_PRIME:
        investible = customer.getSavingsAccount().getBalance() > 1e6; break;
    case PRIME:
        investible = customer.isCeo(); break;
}

私は のすべての使用法を擁護しているわけではswitchありません。しかし、私の意見では、「Switch はコードのにおいがする」などのステートメントは間違っています。他の誰かが同意しますか?

4

22 に答える 22

57

次のようなステートメントだと思います

switch ステートメントを使用するのは、不適切な OOP スタイルです。

Case ステートメントは、ほとんどの場合、ポリモーフィズムに置き換えることができます。

単純化しすぎています。真実は、を切り替えている case ステートメントは OOP スタイルが悪いということです。これらは、ポリモーフィズムに置き換えたいものです。の切り替えは問題ありません。

于 2009-02-15T16:06:36.733 に答える
17

あなたのフォローアップを取る:

これがビジネス ローンを希望する顧客にとっての単なる「投資可能性」ロジックであるとしたらどうなるでしょうか。おそらく、別の製品に対する顧客の投資不可能な決定は、実際にはまったく異なるものです...また、新しい製品が常に登場し、それぞれに異なる投資可能性の決定があり、コアの顧客クラスを毎回更新したくない場合はどうなりますか?これが起こる時?

そしてあなたのコメントの1つ:

ロジックを操作対象のデータの近くに保持することについては、完全にはわかりません。現実の世界はこのようには機能しません。私がローンを申し込むと、銀行は私が適格かどうかを判断します。彼らは私に自分で決めるように求めません。

これに関する限り、あなたは正しいです。

boolean investable = customer.isInvestable();

あなたが話している柔軟性のための最適な解決策ではありません。ただし、元の質問では、個別の Product 基本クラスの存在について言及していませんでした。

現在利用可能な追加情報を考えると、最善の解決策は次のように思われます

boolean investable = product.isInvestable(customer);

投資可能性の決定は、「現実世界」の議論に従って製品によって (ポリモーフィックに!) 行われ、製品を追加するたびに新しい顧客サブクラスを作成する必要もありません。製品は、顧客の公開インターフェースに基づいてその決定を行うために、任意の方法を使用できます。切り替えの必要性を排除するために顧客のインターフェースに適切な追加を行うことができるかどうかはまだ疑問ですが、それでもすべての悪の中で最小のものかもしれません.

ただし、提供されている特定の例では、次のようなことをしたくなるでしょう。

if (customer.getCategory() < PRIME) {
    investable = customer.getSavingsAccount().getBalance() > 1e6;
} else {
    investable = customer.isCeo();
}

これは、スイッチのすべての可能なカテゴリをリストするよりも明確で明確だと思います。「現実世界」の思考プロセスを反映する可能性が高いと思います (「それらはプライム以下ですか?」対「サブプライムまたはミッドプライムですか?」 ?") であり、ある時点で SUPER_PRIME 指定が追加された場合に、このコードを再確認する必要がなくなります。

于 2009-02-15T16:01:16.247 に答える
16

純粋な OO コードでスイッチを使用すると、コードの匂いがします。これは、それらの定義が間違っているという意味ではなく、使用についてよく考える必要があるということです。特に注意してください。

ここでの switch の定義には、switch ステートメントとして簡単に書き直すことができる if-then-else ステートメントも含まれます。

スイッチは、操作対象のデータに近い動作を定義していないこと、たとえばサブタイプのポリモーフィズムを利用していないことを示している可能性があります。

オブジェクト指向言語を使用する場合、オブジェクト指向の方法でプログラミングする必要はありません。したがって、より関数型またはオブジェクトベースのプログラミング スタイルを使用することを選択した場合 (たとえば、よりリッチなドメイン モデルとは対照的に、データのみを含み動作を含まない DTO を使用する場合)、スイッチを使用しても問題はありません。

最後に、オブジェクト指向プログラムを作成するとき、スイッチはオブジェクト指向モデルの「エッジ」で非常に便利です。オブジェクト指向以外の外部の世界からオブジェクト指向モデルに何かが入り、この外部エンティティをオブジェクト指向概念に変換する必要がある場合です。できるだけ早くこれを行うのが最善です。たとえば、データベースの int は、スイッチを使用してオブジェクトに変換できます。

int dbValue = ...;

switch (dbValue)
{
  case 0: return new DogBehaviour();
  case 1: return new CatBehaviour();
  ...
  default: throw new IllegalArgumentException("cannot convert into behaviour:" + dbValue);  
}

いくつかの応答を読んだ後に編集します。

Customer.isInvestable: 素晴らしい、ポリモーフィズム。しかし、今はこのロジックを顧客に関連付けており、さまざまな動作を実装するためだけに、顧客のタイプごとにサブクラスが必要です。前回チェックしたとき、これは継承の使用方法ではありません。顧客のタイプを のプロパティにCustomerするか、顧客のタイプを決定できる関数を持たせる必要があります。

二重ディスパッチ: ポリモーフィズムが 2 回。しかし、あなたの訪問者クラスは本質的に大きなスイッチであり、上で説明したのと同じ問題がいくつかあります。

さらに、OPの例に従って、ポリモーフィズムはCustomerそれ自体ではなく、顧客のカテゴリにある必要があります。

値の切り替えは問題ありません: わかりましたが、範囲やよりエキゾチックな条件をテストできる if-then-else とは対照的に、ほとんどの場合、switch ステートメントは単一intの , char, , ... 値をテストするために使用されます。enumしかし、この単一の値でディスパッチし、上記で説明したようにオブジェクト指向モデルの端にない場合、スイッチは値ではなく型でディスパッチするためによく使用されるようです。または: if -then-else の条件付きロジックをスイッチで置き換えることができない場合は、おそらく大丈夫ですが、そうでない場合はおそらくそうではありません。したがって、OOP のスイッチはコードの匂いだと思います。

型をオンにするのは OOP スタイルではありませんが、値をオンにするのは問題ありません。

自体は単純化しすぎています。

出発点に戻ると、 aswitchは悪くありません。常に非常に OO であるとは限りません。問題を解決するために OO を使用する必要はありません。OOP を使用する場合は、スイッチに特に注意する必要があります。

于 2009-02-15T15:05:32.540 に答える
14

それは悪いOOPスタイルです。

すべての問題が OO で最適に解決されるわけではありません。パターン マッチングが必要な場合もありますが、これはスイッチの貧弱なバージョンです。

于 2009-02-15T14:09:32.880 に答える
12

どちらかといえば、このスタイルのプログラミングを説明する人々にうんざりしています。このスタイルでは、「簡単な」タイプ (顧客、口座、銀行) に多数のゲッターが追加され、有用なコードが「コントローラー」内のシステムにスプレーされます。 "、"ヘルパー"、"ユーティリティ" クラス - オブジェクト指向として。このようなコードOO システムの臭いです。気分を害するのではなく、理由を尋ねる必要があります。

于 2009-02-15T15:32:46.393 に答える
7

確かにスイッチは貧弱な OO です。関数の途中に return を置くべきではありません。魔法の値は不適切です。参照は決して null であってはなりません。条件文は {中かっこ} で囲む必要がありますが、これらはガイドラインです。彼らは宗教的に従うべきではありません。保守容易性、リファクタリング容易性、および理解可能性はすべて非常に重要ですが、実際に仕事を成し遂げるのに次ぐものです。プログラミングの理想主義者になる時間がない場合があります。

プログラマーが有能であると見なされる場合、ガイドラインに従い、利用可能なツールを慎重に使用できると想定する必要があり、常に最良の決定を下すとは限らないことを受け入れる必要があります。彼は最適ではないルートを選択したり、間違いを犯してデバッグが困難な問題に遭遇したりする可能性があります。これは、ヌル ポインターを持つべきではないか、あまりにも多くのヌル ポインターを渡してはならないときにスイッチを選択したためです。それが人生であり、彼は有能であるため、間違いから学びます。

私は宗教的にプログラミングのドグマに従っていません。私は、プログラマーとしての私自身の文脈でガイドラインを検討し、妥当と思われる方法でそれらを適用します。この種のプログラミング手法は、目前の問題の基本的なものでない限り、口を挟むべきではありません。優れたプログラミング手法について意見を述べたい場合は、ブログまたは適切なフォーラム (ここなど) で行うのが最善です。

于 2009-02-15T16:10:41.310 に答える
6

オープンクローズド原則に関するロバートマーチンの記事は、別​​の視点を提供します。

ソフトウェアエンティティ(クラス、モジュール、機能など)は、拡張のために開いている必要がありますが、変更のために閉じている必要があります。

コード例では、顧客の「カテゴリタイプ」を効果的にオンにしています。

boolean investible ;
switch (customer.getCategory()) {
    case SUB_PRIME:
    case MID_PRIME:
        investible = customer.getSavingsAccount().getBalance() > 1e6; break;
    case PRIME:
        investible = customer.isCeo(); break;
}

この現在の状況では、新しい顧客カテゴリが生まれている可能性があります;-)。これは、このクラスを開き、継続的に変更する必要があることを意味します。switchステートメントが1つしかない場合は問題ないかもしれませんが、他の場所で同様のロジックを使用したい場合はどうなりますか。

isInvestibleでメソッドが作成される他の提案ではなくCustomer、Cagtegoryは本格的なクラスになり、これらの決定を行うために使用する必要があります。

boolean investible ;
CustomerCategory category = customer.getCategory();
investible = category.isInvestible(customer);

class PrimeCustomerCategory extends CustomerCategory {
    public boolean isInvestible(Customer customer) {
        return customer.isCeo();
    }
}
于 2009-02-16T14:00:23.080 に答える
5

いくつかのオプションに基づいて決定を下す必要があり、ポリモーフィズムが過剰である場合があります (YAGNI)。この場合、スイッチは問題ありません。Switch は単なるツールであり、他のツールと同じように簡単に使用または悪用できます。

それはあなたがしようとしていることに依存します。ただし、スイッチを使用する場合は、設計が悪いことを示している可能性があるため、慎重に検討する必要があります。

于 2009-02-15T15:30:11.593 に答える
5

タイプの切り替えはコードの匂いだと思います。ただし、コード内の関心の分離に関する懸念を共有します。しかし、それらは多くの方法で解決でき、ポリモーフィズムを引き続き使用できます。たとえば、ビジター パターンなどです。Gang of Four の「Design Patterns」を読んでください。

Customerなどのコア オブジェクトはほとんどの場合固定されたままであるが、操作が頻繁に変更される場合は、操作をオブジェクトとして定義できます。

    interface Operation {
      void handlePrimeCustomer(PrimeCustomer customer);
      void  handleMidPrimeCustomer(MidPrimeCustomer customer);
      void  handleSubPrimeCustomer(SubPrimeCustomer customer);    
    };

    class InvestibleOperation : public Operation {
      void  handlePrimeCustomer(PrimeCustomer customer) {
        bool investible = customer.isCeo();
      }

      void  handleMidPrimeCustomer(MidPrimeCustomer customer) {
        handleSubPrimeCustomer(customer);
      }

      void  handleSubPrimeCustomer(SubPrimeCustomer customer) {
        bool investible = customer.getSavingsAccount().getBalance() > 1e6;    
      }
    };

    class SubPrimeCustomer : public Customer {
      void  doOperation(Operation op) {
        op.handleSubPrimeCustomer(this);
      }
    };

   class PrimeCustomer : public Customer {
      void  doOperation(Operation op) {
        op.handlePrimeCustomer(this);
      }
    };

これはやり過ぎのように見えますが、操作をコレクションとして処理する必要がある場合、多くのコーディングを簡単に節約できます。たとえば、それらすべてをリストに表示し、ユーザーに 1 つを選択させます。操作が関数として定義されている場合、多くのハード コーディングされた switch-case ロジック、別の操作またはここで参照されている製品を追加するたびに更新する必要がある複数の場所で簡単に終了します。

于 2009-02-15T17:12:48.070 に答える
3

私は、switch ステートメントを if/else ブロックのより読みやすい代替手段と考えています。

ロジックを統合的に評価できる構造に煮詰めることができれば、そのコードは OOP で必要なレベルのカプセル化を提供している可能性が高いことがわかりました。

実用的なプログラムを出荷するには、ある時点で実際の (乱雑な) ロジックを作成する必要があります。Java と C# は、C から継承されているため、厳密には OOP 言語ではありません。厳密に OOP コードを強制したい場合は、その考え方に違反するイディオムを提供しない言語を使用する必要があります。私の見解では、Java と C# はどちらも柔軟であることを意図しています。

奇妙なことに、VB6 がこれほど成功した理由の 1 つは、VB6 がオブジェクト指向ではなくオブジェクト ベースであったことです。したがって、実用的なプログラマーは常に概念を組み合わせます。適切なカプセル化が既にプログラムされている限り、スイッチはより管理しやすいコードにつながる可能性もあります。

于 2009-02-15T16:15:08.037 に答える
2

switch ステートメントが不適切な OOP プラクティスであるかどうかは、switch ステートメントを使用している場所によって異なると思います。

たとえば、ファクトリ メソッドでは、複雑で潜在的にバグのあるリフレクション ベースのコードを記述するための非常に単純な代替手段になる場合があります。

ただし、ほとんどの場合、スイッチは単に設計に過ぎないと思います。多くの場合、同じメソッドを使用してさまざまなオブジェクトに操作の複雑さを隠すと、コードがより理解しやすくなり、場合によってはさらに高速になります。たとえば、たくさん実行するスイッチがある場合、事前にパッケージ化されたオブジェクトを使用すると、実際に CPU サイクルをいくらか節約できます。

于 2009-02-16T01:21:26.447 に答える
2

ライブラリを操作することもコードの匂いです。選択の余地はないかもしれませんが、それは良い習慣にはなりません。

于 2009-02-15T14:10:39.380 に答える
2

オブジェクト指向コードで switch ステートメントを使用しても問題はありません。私の唯一の批判は、このロジックを隠した IsInvestible と呼ばれる新しいメソッドを Customer に作成したことです。このメソッドの内部実装として switch ステートメントを使用することに問題はありません。あなたが言ったように、列挙型にメソッドを追加することはできませんが、Customer にさらにメソッドを追加することはできます。

ソースにアクセスできない場合は、非インスタンス メソッドで問題ないと思います。最も純粋な OOP には真新しいオブジェクトが必要ですが、この場合はやり過ぎのようです。

于 2009-02-15T15:03:52.827 に答える
2

あなたがどこから来たのかを知っています。一部の言語では、これを行う必要があります。

String str = getStr();
switch(str) {
case "POST" : this.doPost(); break;
case "GET" : this.doGet(); break;
//and the other http instructions
}

そして今何?確かに、それを行うための優れた OOP の方法があります。

str.request(this);

String を拡張できないのは残念ですが、HttpInstruction ごとに 8 つのサブクラスを持つ HttpInstruction クラスを作成することを検討しています。正直なところ、特にパーサーについて話すときは、かなり難しいです。

確かに、それは良い OOP ではありませんが、良いコードが常に可能であるとは限りません。

ちょっと脱線させてください。私は論文を書いています。私は個人的に、再帰関数の通常のセットアップが好きではありません。通常は funcRec(arg1,arg) と func(arg1):=func(funcRec(arg1,0)); のようになります。

そのため、デフォルトの引数を使用して論文で定義しました。デフォルト引数の概念を誰もが知っているわけではありません。私の論文では疑似コードを使用していますが、教授はアルゴリズムを従来の方法に変更するように指示しました。デフォルトの引数はあまり使用しないためです。不必要に読者を驚かせないでください。彼は正しいと思います。

しかし、その結果、デフォルト引数を配布することを唯一の目的とする関数に行き詰まってしまいました。

要するに、真に美しいプログラムには、優れたライブラリ、優れたコード ブラウザーとツール、FogBugz の品質のバグトラッカー、少なくともより統合された、git 品質のバージョン管理などが必要です。そして、うーん、これらすべてを使用でき、これらすべてを処理する方法を知っているあなたの周りの人々。そして何よりも、トリッキーな問題をエレガントに解決できる美しい言語です。

そのため、すべての状況でスイッチの優れた代替品を見つけるのが困難な Java に行き詰まっている可能性があります。Self にはエレガントなソリューションがあります。しかし、あなたは Self を使用していません。使用していた場合、同僚はそれを読み取ることができないため、忘れてください。

そして今、妥協点を見つけてください。

悲しいですね。

于 2009-02-15T15:20:43.097 に答える
2

コードを取り込んでいないため、外部ソースからのデータは本質的に真のオブジェクト指向にはなりません。ケースが含まれている場合は、ケースがあります。限目。

それを超えて、OOP は特効薬ではありません。答えになる時もあれば、そうでない時もあります。

于 2009-02-16T01:34:04.823 に答える
1

はい、私はそれが悪いスタイルだとあなたに言う人々にうんざりしています。

編集:これは、質問が修正される前の方が理にかなっています。

于 2009-02-15T14:35:00.883 に答える
1

そして今何?確かに、それを行うための優れた OOP の方法があります。

str.request(これ);

String を拡張できないのは残念ですが、HttpInstruction ごとに 8 つのサブクラスを持つ HttpInstruction クラスを作成することを検討しています。正直なところ、特にパーサーについて話すときは、かなり難しいです。

C# 拡張メソッドを試したことがありますか? 紐は伸ばすことができます。

于 2009-02-15T15:26:26.267 に答える
1

Case ステートメントは、ほとんどの場合、ポリモーフィズムに置き換えることができます。

public class NormalCustomer extends Customer {
    public boolean isInvestible() {
        return getSavingsAccount().getBalance() > 1e6;
    }
}

public class PreferredCustomer extends Customer {
    public boolean isInvestible() {
        return isCeo();
    }
}

このアプローチにより、クライアント コードが簡素化されます。クライアント コードは、「投資可能性」の計算方法の詳細を知る必要はなく、Customer オブジェクトの状態を掘り下げて デメテルの法則を破る必要もありません。

于 2009-02-15T15:02:54.200 に答える
1

switch ステートメントに関する私の問題は、実際のアプリケーションでは、分離して存在する switch ステートメントがめったにないことです。

私の会社のコードベースでリファクタリングが必要なコードの多くは、クラス全体が複数の switch ステートメントで埋め尽くされており、すべての switch ステートメントの存在を知る必要がありました。

最終的には、システム全体を Strategy パターンに最もクリーンにリファクタリングし、Factory が switch ステートメントの残りの 1 つのコピーに基づいて戦略の作成を制御します。

時間の制約により、これが私たちのニーズに応えたので、それ以上は取りませんでした. 大きな巨大な switch ステートメントはまだありましたが、1 つしかなかったため、追加の戦略を追加するには、インターフェイスを実装し、マスター switch ステートメントに作成手順を追加するだけで済みました。

于 2009-02-16T01:33:42.953 に答える
0

「また、新製品が常に登場し、それぞれが異なる投資可能性の決定を下しており、これが発生するたびにコアの顧客クラスを更新したくない場合はどうすればよいでしょうか?」

これは思い浮かびます:

インターフェイス投資可能
{
    boolean isIvestible(顧客 c);
}

クラスFooInvestible
    投資可能
{
    public boolean isInvestible(最終顧客 c)
    {
        // スイッチであれ、その他のロジックであれ、
    }
}

swtich の最初の使用と新しいタイプの決定の追加に関する「問題」は、健全な方法で維持することが不可能なコードの巨大なネズミの巣に巻き込まれる可能性があることです。意思決定をクラスに分割すると、意思決定が強制的に分割されます。そうすれば、スイッチを使用しても、コードはより健全で保守しやすいままになる可能性があります。

于 2009-02-15T17:50:19.557 に答える
0

Case ステートメントは、ほとんどの場合、ポリモーフィズムに置き換えることができます

また、

boolean investable = customer.isInvestable();

isInvestable への呼び出しは多態的であるため、呼び出しを行うために使用される実際のアルゴリズムは、顧客のタイプによって決まります。

私はあなたが両方とも間違っていると思います。これがビジネス ローンを希望する顧客にとっての単なる「投資可能性」ロジックであるとしたらどうなるでしょうか。おそらく、顧客が別の製品を購入できないという決定は、実際にはまったく異なるものであり、おそらく「カテゴリ」に基づいているのではなく、住んでいる場所、結婚しているかどうか、どの職種で働いているかなどに基づいているのでしょうか?

Customerまた、新製品が常に登場し、それぞれに異なる投資可能性の決定があり、これが発生するたびにコアクラスを更新したくない場合はどうなりますか?

私が言ったように、私switchは常に大丈夫だと言っているわけではありませんが、同様に、完全に正当である可能性があります. うまく使えば、アプリケーション ロジックを非常に明確に記述することができます。

于 2009-02-15T15:16:00.410 に答える