23個のインターフェイスを実装するクラスを持つことは、おそらくコードの匂いやアンチパターンであると考えるでしょう。それが本当にアンチパターンである場合、あなたはそれを何と呼びますか?それとも、単に単一責任の原則に従っていないだけですか?
23個のインターフェイスを実装するクラスを持つことは、おそらくコードの匂いやアンチパターンであると考えるでしょう。それが本当にアンチパターンである場合、あなたはそれを何と呼びますか?それとも、単に単一責任の原則に従っていないだけですか?
回答:
ロイヤルファミリー:彼らは特にクレイジーなことはしませんが、10億のタイトルを持ち、何らかの形で他のほとんどのロイヤルに関連しています。
somehow
私は、このアンチパターンを「すべての取引のジャック」または「Too Many Hats」と命名することを提案します。
名前を付けなければならないなら、私はそれをハイドラと呼ぶだろうと思う:
ギリシャ神話では、Lernaean Hydra(ギリシャ語:ΛερναίαὝδρα)は古代の無名の蛇のような地質の水獣でした。ペイントし、頭を切り落とすごとにさらに2つずつ成長しました。そして、毒の息は彼女の足跡さえ致命的だったので非常に強烈でした。
特に重要なのは、頭がたくさんあるだけでなく、頭がどんどん増え続け、そのために殺すことができないという事実です。それが、こうしたデザインの私の経験でした。開発者は、画面に収まらないほど多くのインターフェイスをジャムし続け、それまでにプログラムの設計と仮定に深く根付いてしまい、それを分割することは絶望的な見通しです(試してみると、実際には多くの場合、ギャップを埋めるためにより多くのインターフェースが必要になります)。
「Hydra」による差し迫った破滅の初期兆候の1つは、次のように、多くの健全性チェックなしで、多くの場合、意味のないターゲットに1つのインターフェイスを別のインターフェイスにキャストすることです。
public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
return ((IAssembler)locator).BuildWidget(partsNeeded);
}
コンテキストから外れると、このコードについて何か怪しいものがあることは明らかですが、この発生の可能性は、開発者が常に同じクリーチャーを扱っていることを直感的に知っているため、オブジェクトが成長する「頭」が増えるにつれて上がります。
幸運は、これまでその実装23のインターフェースオブジェクト上の任意のメンテナンスを行っています。プロセスで付随的な損傷を引き起こさない可能性は、ほとんどありません。
神オブジェクトが頭に浮かぶ。すべてを行う方法を知っている単一のオブジェクト。これは、2つの主要な設計方法論の「凝集」要件への順守が低いことに起因しています。23個のインターフェイスを持つオブジェクトがある場合、ユーザーに対して23個の異なるものになる方法を知っているオブジェクトがあり、それらの23個の異なるものは、システムの単一のタスクまたは領域のラインに沿っていない可能性があります。
SOLIDでは、このオブジェクトの作成者は明らかにインターフェイス分離の原則に従うことを試みましたが、以前のルールに違反しています。単一責任の原則。「ソリッド」である理由があります。設計について考えるときは、Sが常に最初になり、他のすべてのルールに従います。
GRASPでは、そのようなクラスの作成者は「高凝集」ルールを無視しました。GRASPは、SOLIDとは異なり、オブジェクトには単一の責任があるわけではありませんが、せいぜい2つまたは3つの非常に密接に関連する責任を持つ必要があると教えています。
23は単なる数字です!最もありそうなシナリオでは、それは警告するのに十分高いです。ただし、「アンチパターン」として呼び出されるタグを取得する前に、メソッド/インターフェイスの最大数は何ですか?5、10、25ですか?10が適切な場合、11も有効である可能性があるため、数値は実際には答えではないことがわかります。
本当の問題は複雑さです。また、長いコード、メソッドの数、または何らかのメジャーによるクラスの大きなサイズは、実際には複雑さの定義ではないことを指摘する必要があります。はい、コードが大きくなると(メソッドの数が増えると)、新しい初心者にとって読みやすく、把握しにくくなります。また、潜在的に多様な機能、多数の例外、およびさまざまなシナリオ向けの非常に進化したアルゴリズムも処理します。これは複雑であることを意味するものではありません-消化するのが難しいだけです。
一方、数時間で読み書きできる比較的小さなサイズのコードは依然として複雑です。ここで、コードが(不必要に)複雑であると思います。
オブジェクト指向設計のあらゆる知恵をここに入れて「複雑」を定義することができますが、「非常に多くのメソッド」が複雑さを示していることを示すためにここで制限します。
相互知識。(別名カップリング) 多くの場合、物事がクラスとして記述されるとき、私たちは皆、それが「素敵な」オブジェクト指向コードであると考えています。しかし、他のクラスに関する前提は、実際に必要なカプセル化を本質的に破ります。アルゴリズムの内部状態に関する詳細を「リーク」するメソッドがある場合-アプリケーションは、サービングクラスの内部状態に関する重要な仮定で構築されます。
繰り返しが多すぎる(侵入) 似た名前を持つが、矛盾する作業を行うメソッド、または同様の機能を持つ矛盾する名前のメソッド。多くの場合、コードはさまざまなアプリケーション用にわずかに異なるインターフェイスをサポートするように進化します。
ロールが多すぎる クラスがサイド関数を追加し続け、好きなように拡張し続けると、クラスが実際に2つのクラスになったことがわかります。驚くべきことに、これらはすべて本物から始まりますこれを行うための要件と他のクラスは存在しません。これを考慮して、トランザクションの詳細を伝えるクラスTransactionがあります。これまでのところよさそうだ。今では誰かが「トランザクションの時間」(UTCなど)でフォーマット変換を必要とし、後で、人々は特定の事柄が特定の日付にあったかどうかをチェックするルールを追加してトランザクションを無効にします。-ストーリー全体を書くことはしませんが、最終的にはトランザクションクラスでカレンダー全体を構築し、その後、人々はその「カレンダーのみ」の部分を使用し始めます!これは非常に複雑です(想像してみてください)なぜ「トランザクションクラス」をインスタンス化して、「カレンダー」が提供する機能を持たせるのでしょうか。
book_a_ticket()を実行すると、APIの一貫性が失われます -チケットを予約します!それは、それを実現するためにいくつのチェックとプロセスが行われるかに関係なく、非常に簡単です。現在、時間の流れがこれに影響を及ぼし始めると複雑になります。通常、「検索」と可能な/使用不可のステータスを許可してから、バックnを減らすために、チケット内にいくつかのコンテキストポインターの保存を開始し、それを参照してチケットを予約します。検索だけが機能ではありません。このような「サイド機能」が多くなると事態は悪化します。プロセスでは、book_a_ticket()の意味はbook_that_ticket()を意味します!そしてそれは想像を絶するほど複雑になることがあります。
おそらく、非常に進化したコードで見られるような多くの状況があり、多くの人がシナリオを追加できると確信しています。「非常に多くのメソッド」が意味をなさないか、明らかに考えていることをしない。これはアンチパターンです。
私の個人的な経験では、合理的にボトムアップで開始するプロジェクトでは、本来のクラスが本来あるべきものの多くが埋められたままであるか、さらに悪いことに、異なるクラスに分割されたままで、結合が増加します。ほとんどの場合、10のクラスに値すると思われますが、4つしかありませんが、それらの多くは多目的で混乱し、多数のメソッドを持っている可能性があります。それを神とドラゴンと呼んでください。これが悪いことです。
しかし、きちんと一貫性のある非常に大きなクラスに出くわします。それらには30のメソッドがありますが、それでも非常にクリーンです。彼らは良いことができます。
クラスには、正確な数のインターフェイスが必要です。これ以上でもそれ以下でもありません。
「多すぎる」と言うことは、これらのインターフェイスのすべてがそのクラスで有用な目的を果たしているかどうかを見ないと不可能です。オブジェクトがインターフェイスを実装することを宣言することは、クラスがコンパイルされることを期待する場合、そのメソッドを実装する必要があることを意味します。(私が見ているクラスはそうだと思います。)インターフェイスのすべてが実装され、それらの実装がクラスの内部に対して何かを行う場合、実装がそこにあるべきではないと言うのは難しいでしょう。私が考えることができるのは、これらの基準を満たすインターフェースがどこに属さないかということです。それは外部です:誰も使用しないときです。
一部の言語では、Javaのextends
キーワードのようなメカニズムを使用してインターフェイスを「サブクラス化」できますが、それを書いた人は誰も知らなかったかもしれません。また、23個すべてが十分に離れているため、それらを集約しても意味がありません。
典型的な「bean」の通常のように、プロパティごとに「getter」/「setter」のペアがあると考えられ、これらすべてのメソッドをインターフェースに昇格させました。それでは、「ビーンズを持っている」と呼ぶのはどうでしょう。または、あまりにも多くの豆のよく知られた影響の後の、より多くのラベラス「Flatulence」。
さまざまな環境で汎用オブジェクトを使用すると、インターフェイスの数が増えることがよくあります。C#では、IComparable、IEqualityComparer、およびIComparerのバリアントにより、個別のセットアップで並べ替えが可能になります。そのため、すべてを実装することになります。 、さらに、複数のジェネリックを実装できます。
シナリオの例を見てみましょう。クレジットを購入できるWebショップで、他の何かを購入することができます(ストックフォトサイトはこのスキームをよく使用します)。クラス「Valuta」とクラス「Credits」が同じベースを継承する場合があります。Valutaには、「通貨」プロパティを気にせずに計算を実行できる(たとえば、ポンドにドルを追加する)演算子のオーバーロードと比較ルーチンがあります。クレジットはより単純ですが、他の明確な動作がいくつかあります。これらを相互に比較できるようにするには、IComparableとIComparableおよび比較インターフェイスの他のバリアントを両方に実装します(基本クラスまたは他の場所にあるかどうかにかかわらず、共通の実装を使用します)。
シリアル化を実装すると、ISerializable、IDeserializationCallbackが実装されます。次に、やり直し元に戻すスタックを実装します。IClonableが追加されます。IsDirty機能:IObservable、INotifyPropertyChanged。ユーザーが文字列を使用して値を編集できるようにします:IConvertable ...リストは延々と続くことができます...
現代の言語では、これらの側面を分離し、コアクラス以外の独自のクラスに配置するのに役立つ別の傾向が見られます。次に、外部クラスまたはアスペクトは、注釈(属性)を使用してターゲットクラスに関連付けられます。多くの場合、外部アスペクトクラスを多かれ少なかれ汎用的にすることが可能です。
属性(注釈)の使用はリフレクションの対象となります。1つの欠点は、軽微な(初期)パフォーマンスの低下です。(しばしば感情的な)欠点は、カプセル化などの原則を緩和する必要があることです。
常に他の解決策がありますが、すべての気の利いた解決策にはトレードオフまたはキャッチがあります。たとえば、ORMソリューションを使用するには、すべてのプロパティを仮想として宣言する必要があります。シリアル化ソリューションでは、クラスのデフォルトコンストラクターが必要になる場合があります。依存性注入を利用している場合、単一のクラスに23個のインターフェースを実装することになります。
私の目には、23のインターフェイスが定義上悪いものである必要はありません。その背後によく考えられたスキーム、またはリフレクションの使用や極端なカプセル化の信念の回避など、いくつかの原則的な決定があるかもしれません。
ジョブを切り替えるとき、または既存のアーキテクチャに基づいて構築する必要があるとき。私のアドバイスは、最初に完全に知り合うことであり、すべてをリファクタリングしようとしないでください。元の開発者(彼がまだいる場合)を聞いて、あなたが見ているものの背後にある考えやアイデアを理解してください。質問するときは、それを分解するためではなく、学ぶために行います...はい、誰もが自分の金色のハンマーを持っていますが、より多くのハンマーを集めることができれば仲間の仲間と仲良くなります。