単一の(パブリック)メソッドのみを持つクラスは問題ですか?


51

現在、ビデオ監視映像の圧縮とインデックス作成を実行するソフトウェアプロジェクトに取り組んでいます。圧縮は、背景オブジェクトと前景オブジェクトを分割し、背景を静的画像として保存し、前景をスプライトとして保存することで機能します。

最近、私はプロジェクトのために設計したいくつかのクラスのレビューに着手しました。

パブリックメソッドが1つしかないクラスがたくさんあることに気付きました。これらのクラスの一部は次のとおりです。

  • VideoCompressor(compressタイプの入力ビデオを取り込み、タイプRawVideoの出力ビデオを返すメソッドを使用CompressedVideo)。
  • VideoSplitter(splitタイプの入力ビデオを取り込みRawVideo、それぞれタイプの2つの出力ビデオのベクトルを返すメソッドを使用RawVideo)。
  • VideoIndexer(indexタイプの入力ビデオを取り込み、タイプRawVideoのビデオインデックスを返すメソッドを使用VideoIndex)。

私は自分自身だけのような呼び出しを行うために、各クラスをインスタンス化見つけますVideoCompressor.compress(...)VideoSplitter.split(...)VideoIndexer.index(...)

表面的には、クラス名は意図する機能を十分に説明していると思います。実際には名詞です。同様に、それらのメソッドも動詞です。

これは実際に問題ですか?


14
言語に依存します。C ++やPythonのようなマルチパラダイム言語では、これらのクラスにはビジネスが存在しません。それらの「メソッド」は自由な関数でなければなりません。

3
@delnan:C ++であっても、完全な「OO機能」を必要としない場合でも、通常はモジュールの作成にクラスを使用します。実際、「フリー関数」をモジュールにグループ化する代わりに名前空間を使用する代替手段がありますが、その利点は見当たりません。
ドックブラウン14年

5
@DocBrown:C ++には名前空間があります。実際、現代のC ++では、メソッドに対しては(静的に)オーバーロードできますが、メソッドはインボカントに対してのみオーバーロードできるため、メソッドには無料の関数をよく使用します。
-Jan Hudec 14

2
@DocBrown:それは質問の核心です。関数が十分に複雑な場合、「外部」メソッドを1つだけ持つ名前空間を使用するモジュールは完全に問題ありません。名前空間は何かを表すふりをしないし、オブジェクト指向のふりもしないからです。クラスは実行しますが、このようなクラスは実際には単なる名前空間です。もちろん、Javaでは関数を使用できないため、これが結果です。Javaの恥。
-Jan Hudec 14

回答:


87

いいえ、これは問題ではなく、まったく逆です。これはモジュール性の兆候であり、クラスの明確な責任です。無駄のないインターフェイスは、そのクラスのユーザーの観点から把握しやすく、疎結合を促進します。これには多くの利点がありますが、ほとんど欠点はありません。もっと多くのコンポーネントがそのように設計されることを望みます!


6
これは正しい答えであり、私はそれを支持しましたが、いくつかの利点を説明することでより良くすることができます。OPの本能が彼に問題だと言っていることは、他の何かだと思います...つまり、VideoCompressorは本当にインターフェースです。Mp4VideoCompressorはクラスであり、VideoSplitterのコードを新しいクラスにコピーすることなく、別のVideoCompressorと交換できる必要があります。
pdr 14年

1
申し訳ありませんが、あなたは間違っています。単一のメソッドを持つクラスは、スタンドアロン関数でなければなりません。
マイルルーティング14

3
@MilesRout:あなたのコメントはあなたが質問を誤解していることを示しています-OPは1つのメソッドではなく1つのパブリックメソッドを持つクラスについて書いています
Doc Brown 14

2
「単一のメソッドを持つクラスは、スタンドアロン関数でなければなりません。」 そのメソッドが何であるかについての大まかな仮定。1つのパブリックメソッドを持つクラスがあります。その背後にあるのは、そのクラスの複数のメソッドに加えて、クライアントをまったく知らない5つのクラスです。SRPの優れたアプリケーションは、階層化されたクラス構造のクリーンでシンプルなインターフェイスを生成し、そのシンプルさが最上位に現れます。
レーダーボブ

2
@Andy:問題は「これは問題ですか」であり、「これは特定のオブジェクト指向の定義に適合していますか」ではありませんでした。さらに、OPが状態のないクラスを意味するという質問には、まったく兆候がありません。
Doc Brown

26

オブジェクト指向ではなくなりました。これらのクラスは何も表さないので、それらは機能の単なる容器です。

それが間違っているという意味ではありません。機能が十分に複雑である場合、または汎用の場合(つまり、引数が具体的な最終型ではなくインターフェイスである場合)、その機能を別のmoduleに入れることは理にかなっています

そこからあなたの言語に依存します。言語に無料の関数がある場合、関数をエクスポートするモジュールである必要があります。なぜそうではないのにクラスのふりをするのか。言語にJavaなどの無料の機能がない場合は、単一のパブリックメソッドでクラスを作成します。まあ、それはオブジェクト指向設計の限界を示しています。機能的であることが単に一致する場合があります。

1つのパブリックメソッドを持つインターフェイスを実装する必要があるため、1つのパブリックメソッドを持つクラスが必要になる場合があります。オブザーバーパターンまたは依存性注入などに使用します。ここでも、言語に依存します。ファーストクラスのファンクター(C ++(std::functionまたはテンプレートパラメーター)、C#(デリゲート)、Python、Perl、Ruby(proc)、Lisp、Haskellなど)がある言語では、これらのパターンは関数型を使用し、クラスを必要としません。Javaには(まだ、バージョン8では)関数タイプがないため、単一メソッドインターフェイスと対応する単一メソッドクラスを使用します。

もちろん、私は単一の巨大な関数を書くことを支持していません。プライベートサブルーチンを使用する必要がありますが、実装ファイル(C ++のファイルレベルの静的または匿名の名前空間)またはパブリック関数内でのみインスタンス化されるプライベートヘルパークラス(データを保存するかどうか?)に対してプライベートにすることができます。


12
「なぜそうではないのに、クラスのふりをするのか」無料の関数の代わりにオブジェクトを使用すると、状態とサブタイプを許可します。これは、要件が変化する世界で価値があると証明できます。遅かれ早かれ、ビデオ圧縮設定で遊ぶか、代替圧縮アルゴリズムを提供する必要があります。その前に、「クラス」という言葉は、この関数が明確な責任を持つ容易に拡張可能で交換可能なソフトウェアモジュールに属することを単に示しています。それはオブジェクト指向が本当に目指していることではありませんか?
から来る

1
まあ、パブリックメソッドが1つしかない場合(および保護されたメソッドがないことを意味する場合)、実際に拡張することはできません。もちろん、圧縮パラメータをパックすることは理にかなっているかもしれません。その場合、それは関数オブジェクトになります(いくつかの言語はそれらを個別にサポートしていますが、そうでない言語もあります)。
ジャン・ヒューデック

3
これは、オブジェクトと関数の間の基本的な接続です。関数は、単一のメソッドを持ち、フィールドを持たないオブジェクトと同型です。クロージャは、単一のメソッドといくつかのフィールドを持つオブジェクトと同型です。同じ変数セットをすべて閉じるいくつかの関数の1つを返すセレクター関数は、オブジェクトと同型です。(実際、これはJavaScriptでオブジェクトをエンコードする方法です。ただし、セレクター関数の代わりに辞書データ構造を使用する場合を除きます。)
ヨルグWミットタグ14年

4
「もはやオブジェクト指向ではありません。これらのクラスは何も表さないため、関数の単なる容器です。」- これは間違っています。私は、このアプローチがよりオブジェクト指向であることを主張します。オブジェクト指向は、実世界のオブジェクトを表すという意味ではありません。大量のプログラミングは抽象的な概念を扱っていますが、これはオブジェクト指向アプローチを適用して解決できないことを意味しますか?絶対にありません。それはモジュール性と抽象化に関するものです。各オブジェクトには単一の責任があります。これにより、プログラムを簡単に変更して変更できます。OOPの多数の利点をリストするのに十分なスペースがありません。
デスペルター14年

2
@Phoshi:はい、わかりました。機能的なアプローチがうまくいかないと主張したことはありません。ただし、それは明らかにトピックではありません。ビデオコンプレッサーまたはビデオトランスモグリファイヤ、またはそれでもオブジェクトの完全に有効な候補です。
から来た

13

特定のメソッドを専用クラスに抽出する理由があるかもしれません。これらの理由の1つは、依存性注入を許可することです。

VideoExporter最終的にビデオを圧縮できるというクラスがあるとします。きれいな方法は、インターフェースを持つことです:

interface IVideoCompressor
{
    Stream compress(Video video);
}

次のように実装されます。

class MpegVideoCompressor : IVideoCompressor
{
    // ...
}

class FlashVideoCompressor : IVideoCompressor
{
    // ...
}

次のように使用します:

class VideoExporter
{
    // ...
    void export(Destination destination, IVideoCompressor compressor)
    {
        // ...
        destination = compressor(this.source);
        // ...
    }
    // ...
}

悪い選択肢は持っているだろうVideoExporterpublicメソッドをたくさん持っているし、圧縮を含め、すべての仕事をしていませんいます。すぐにメンテナンスの悪夢になり、他のビデオ形式のサポートを追加するのが難しくなります。


2
あなたの答えは、パブリックメソッドとプライベートメソッドを区別しません。クラスのすべてのコードを1つのメソッドだけにする推奨事項として理解できますが、それはあなたが意図したものではないと思いますか?
Doc Brown 14年

2
@DocBrown:プライベートメソッドはこの質問には関係ありません。内部ヘルパークラスなどに配置できます。
-Jan Hudec 14

2
@JanHudec:現在、テキストは「悪い選択肢は多くのメソッドを持つVideoExporterを持つことです」-「悪い選択肢は多くのパブリックメソッドを持つVideoExporterを持つこと」です。
Doc Brown 14年

@DocBrown:同意します。
-Jan Hudec 14

2
@DocBrown:ご意見ありがとうございます。回答を編集しました。もともと、私はそれがその質問(そして私の答え)がパブリックメソッドのみに関するものであると仮定されていると思っていました。それほど明白ではないようです。
Arseni Mourzenko

6

これは、関数を引数として他の関数に渡したいというサインです。あなたの言語(Java?)はそれをサポートしていないと思います。その場合、選択した言語の欠点であるため、設計の失敗ではありません。これは、すべてがクラスでなければならないと主張する言語の最大の問題の1つです。

これらのフェイク関数を実際に渡していない場合は、ただ自由/静的関数が必要です。


1
Java 8以降、ラムダがあるため、ほとんどの場合、関数を渡すことができます。
シルビウブルシア14年

公正な点ですが、それはしばらくの間公式にリリースされず、一部の職場は新しいバージョンへの移行が遅いです。
ドーバル14年

発売日は3月です。また、EAPビルドはJDK 8で非常に人気がありました:)
Silviu Burcea 14年

Java 8ラムダは、わずかな定型コードを保存する以外に、1つのパブリックメソッドのみでオブジェクトを定義するための簡単な方法であるため、ここではまったく違いはありません。
ジュール

5

私はパーティーに遅れていることを知っていますが、誰もがこれを指摘するのを逃したようです:

これは、戦略パターンと呼ばれるよく知られたデザインパターンです。

戦略パターンは、サブ問題を解決するためのいくつかの可能な戦略がある場合に使用されます。通常、すべての実装で契約を実施するインターフェイスを定義し、何らかの形式の依存性注入を使用して具体的な戦略を提供します。

例えば、この場合には、あなたが持っている可能性がありinterface VideoCompressor、その後、例えば、いくつかの代替実装を持っているclass H264Compressor implements VideoCompressorclass XVidCompressor implements VideoCompressor。関係するインターフェイスがあるかどうかは、OPからは明らかではありません。たとえ存在しなくても、必要に応じて元の作者が戦略パターンを実装するためにドアを開けたままになっているだけかもしれません。これ自体が優れたデザインでもあります。

OPが常にメソッドを呼び出すためにクラスをインスタンス化することに気づく問題は、依存性注入と戦略パターンを正しく使用していないという問題です。必要な場所でインスタンス化する代わりに、包含クラスにはストラテジオブジェクトを持つメンバーが必要です。そして、このメンバーは、例えばコンストラクターで注入されるべきです。

多くの場合、ストラテジーパターンは、1つだけのdoStuff(...)メソッドを使用して(表示されているように)インターフェイスクラスになります。


1
public interface IVideoProcessor
{
   void Split();

   void Compress();

   void Index();
}

あなたが持っているものはモジュール式であり、それは良いことですが、もしこれらの責任をIVideoProcessorにグループ化するなら、それはおそらくDDDの観点からもっと理にかなっているでしょう。

一方、分割、圧縮、およびインデックス付けが何らかの方法で関連していなかった場合、それらを個別のコンポーネントとして保持するよりも。


これらの各機能を変更する必要がある理由は多少異なるため、このようにまとめるとSRPに違反すると主張します。
ジュール

0

それは問題です-あなたはデータではなく、デザインの機能面から作業しています。あなたが実際に持っているのは、オブジェクト指向化された3つのスタンドアロン関数です。

たとえば、VideoCompressorクラスがあります。ビデオを圧縮するように設計されたクラスを使用しているのはなぜですか?このタイプの各オブジェクトに含まれる(ビデオ)データを圧縮するメソッドを持つVideoクラスがないのはなぜですか?

OOシステムを設計するときは、適用できるアクティビティを表すクラスではなく、オブジェクトを表すクラスを作成するのが最善です。昔は、クラスはタイプと呼ばれていました-オブジェクト指向は、新しいデータタイプをサポートして言語を拡張する方法でした。OOをこのように考えると、クラスを設計するより良い方法が得られます。

編集:

concatメソッドを持つ文字列クラスを想像してみてください。クラスからインスタンス化された各オブジェクトに文字列データが含まれるようなものを実装できます。

string mystring("Hello"); 
mystring.concat("World");

しかし、OPは次のよ​​うに動作することを望んでいます。

string mystring();
string result = mystring.concat("Hello", "World");

現在、クラスを使用して関連関数のコレクションを保持できる場所がありますが、それはオブジェクト指向ではありません。言語のオブジェクト指向機能を使用してコードベースをよりよく管理するのに便利な方法ですが、 「OOデザイン」の。このような場合のオブジェクトは完全に人工的であり、この種の問題を管理するための優れた言語を言語が提供しないため、単純にこのように使用されます。例えば。C#などの言語では、静的クラスを使用してこの機能を提供します。クラスメカニズムを再利用しますが、メソッドを呼び出すためだけにオブジェクトをインスタンス化する必要はなくなりました。あなたはstring.IsNullOrEmpty(mystring)私がに比べて悪いと思うようなメソッドになるのmystring.isNullOrEmpty()です。

したがって、「クラスをどのように設計すればよいか」という質問がある場合は、クラスに含まれる関数ではなく、クラスに含まれるデータについて考えることをお勧めします。「クラスはメソッドの束」に進むと、「より良いC」スタイルのコードを書くことになります。(Cコードを改善する場合、必ずしも悪いことではありません)が、最高のオブジェクト指向設計プログラムを提供するつもりはありません。


9
-1、まったく同意しません。初心者向けのOO設計は、aのようなデータオブジェクトでのみVideo機能し、そのようなクラスに機能性を打ち負かす傾向があります。高度なオブジェクト指向設計では、機能がのような小さな単位に分割されVideoCompressorます(VideoクラスをのデータクラスまたはファサードにすることができますVideoCompressor)。
Doc Brown 14年

5
@DocBrown:ただし、VideoCompressorオブジェクトを表していないため、オブジェクト指向設計ではなくなりました。それには何の問題もありません。オブジェクト指向設計の限界を示しています。
-Jan Hudec 14

3
しかし、1つの関数がクラスに変換される初心者向けのオブジェクト指向は、実際にはオブジェクト指向ではなく、だれも維持できないクラスのファイルが1000個になる大規模な分離を促進するだけです。クラスを「機能ラッパー」ではなく「データラッパー」と考えるのが最善だと思います。これにより、初心者はオブジェクト指向プログラムについての考え方をよりよく理解できます。
gbjbaanb 14年

4
@DocBrownは実際に私の懸念を正確に強調しました。確かにVideoクラスはありますが、圧縮方法論は決して些細なものではないため、実際にはcompressメソッドを他の複数のプライベートメソッドに分解します。これは、読んだ後、私の質問の理由の一部であるクラスの動詞作成しないでください
yjwong

2
私はそれを持ってしても大丈夫かもしれないと思うVideoクラスを、それがされるだろう構成VideoCompressorVideoSplitter良好なOOの形で、非常に粘着性の個々のクラスである必要があり、そして他の関連するクラス、。
エリックキング14年

-1

ISP(インターフェイス分離の原則は)何のクライアントが使用していない方法に依存することを強制すべきではないと述べています。利点は複数あり、明確です。あなたのアプローチはISPを完全に尊重しており、それは良いことです。

ISPを尊重する別のアプローチは、たとえば、各メソッド(または凝集度の高いメソッドのセット)ごとにインターフェイスを作成し、それらすべてのインターフェイスを実装する単一のクラスを持つことです。これがより良いソリューションであるかどうかは、シナリオによって異なります。これの利点は、依存関係注入を使用する場合、異なるコラボレーター(各インターフェイスに1つ)を持つクライアントを使用できることですが、最終的にはすべてのコラボレーターが同じオブジェクトインスタンスを指すようになります。

ところで、あなたは言った

私は各クラスをインスタンス化しています

、これらのクラスはサービスのようです(したがって、ステートレスです)。それらをシングルトンにすることを考えましたか?



3
インターフェース分離の原則は良いパターンですが、残りについては、この質問はインターフェースではなく実装に関するものであるため、関連性があるかどうかはわかりません。
ジャン・ヒューデック

3
シングルトンがパターンであるかアンチパターンであるかを議論するつもりはありません。私はその問題の可能な解決策(名前さえ持っている)を提案しました。それが適合するかどうかを決めるのは彼次第です。
ディエゴムタシス14年

ISPが関連しているかどうかに関しては、問題の主題は「単一のメソッドのみを持つクラスが問題ですか?」であり、その反対は多くのメソッドを持つクラスです。クライアントはそれに直接依存しています...まさに、Robert Martinのゼロックスの例で、ISPを策定するのに彼がかかりました。
ディエゴムタシス14年

-1

はい、問題があります。しかし、深刻ではありません。このようにコードを構築できれば、悪いことは何も起こらず、メンテナンス可能です。しかし、そのような構造化にはいくつかの弱点があります。たとえば、ビデオの表現(あなたの場合はRawVideoクラスにグループ化されている)が変更された場合、すべての操作クラスを更新する必要があります。または、実行時に異なるビデオの複数の表現があるかもしれないと考えてください。次に、表現を特定の「操作」クラスに一致させる必要があります。また、主観的には、smthで実行する各操作の依存関係をドラッグするのは面倒です。また、操作が不要になった、または新しい操作が必要になったと判断するたびに、渡される依存関係のリストを更新します。

また、それは実際にはSRPの違反です。一部の人々は、SRPを責任分担のガイドとして扱います(そして、各操作を明確な責任として扱うことでそれをはるかに引き延ばします)が、SRPはグループ化責任のガイドでもあることを忘れています。また、同じ理由で変更されるSRPの責任に従ってグループ化する必要があります。これにより、変更が発生した場合、できる限り少ないクラス/モジュールにローカライズされます。大きなクラスに関しては、それらのアルゴリズムが関連している限り(つまり、そのクラスの外部で知られてはならない知識を共有している限り)、同じクラスに複数のアルゴリズムを持つことは問題ではありません。問題は、何らかの方法で関係がなく、さまざまな理由で変化/変化するアルゴリズムを持つ大きなクラスです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.