単一のメソッドを持つクラス—最善のアプローチ?


172

たとえば、単一の機能を実行するためのクラスがあるとします。機能を実行した後、破棄することができます。これらのアプローチのいずれかを選ぶ理由はありますか?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

さまざまな状況のガイドラインを取得しようとするために、詳細について意図的に漠然としていました。しかし、私はMath.random()のような単純なライブラリ関数を実際には考えていませんでした。特定の複雑なタスクを実行するクラスをもっと考えていますが、それを行うには1つの(パブリック)メソッドのみが必要です。

回答:


264

静的メソッドで満たされたユーティリティクラスが大好きでした。彼らは、さもなければ冗長性とメンテナンスの地獄を引き起こすことの周りにあるヘルパーメソッドの素晴らしい統合を作りました。それらは非常に使いやすく、インスタンス化も廃棄もありません。ただ火をお忘れなく。これは、サービス指向アーキテクチャーを作成するための私の最初の無意識の試みだったと思います。しかし、システムが成長するにつれて、ドラゴンがやってきます。

ポリモーフィズム
幸福にうなり声を上げるUtilityClass.SomeMethodメソッドがあるとします。突然、機能を少し変更する必要があります。ほとんどの機能は同じですが、それでもいくつかの部分を変更する必要があります。静的メソッドでなければ、派生クラスを作成し、必要に応じてメソッドの内容を変更できます。静的メソッドなので、できません。もちろん、古いメソッドの前または後に機能を追加する必要がある場合は、新しいクラスを作成して、その中にある古いクラスを呼び出すことができます。

インターフェースの問題
静的なメソッドは、論理的な理由により、インターフェースを介して定義できません。また、静的メソッドをオーバーライドすることはできないため、静的クラスをインターフェイスで渡す必要がある場合は、静的クラスは役に立ちません。これにより、戦略パターンの一部として静的クラスを使用できなくなります。インターフェースの代わりにデリゲートを渡すことにより、いくつかの問題にパッチを当てるかもしれません。

テスト
これは基本的に、上記のインターフェースの問題と関連しています。実装を交換する能力は非常に限られているため、製品コードをテストコードに置き換えるのも困難です。繰り返しになりますが、それらをラップすることはできますが、実際のオブジェクトの代わりにラッパーを受け入れることができるようにするために、コードの大部分を変更する必要があります。

ブロブを促進する
静的メソッドは通常ユーティリティメソッドとして使用され、ユーティリティメソッドは通常異なる目的を持つため、一貫性のない機能で満たされた大きなクラスにすぐに到達します-理想的には、各クラスはシステム内で単一の目的を持つ必要があります。目的が明確に定義されている限り、クラスは5倍にしたほうがいいです。

パラメータクリープ
そもそも、かわいくて無邪気な静的メソッドは、単一のパラメーターを取るかもしれません。機能が成長するにつれて、いくつかの新しいパラメーターが追加されます。間もなくオプションのパラメーターが追加されるため、メソッドのオーバーロードを作成します(または、パラメーターをサポートする言語でデフォルト値を追加します)。やがて、10個のパラメーターを取るメソッドがあります。最初の3つだけが本当に必要で、パラメーター4から7はオプションです。しかし、パラメーター6が指定されている場合、7〜9も入力する必要があります...この静的メソッドが行ったことを単一の目的で行うクラスを作成した場合、必要なパラメーターをコンストラクターを使用して、ユーザーがプロパティを介してオプションの値を設定したり、複数の相互依存値を同時に設定したりするメソッドを使用できます。また、メソッドがこの程度の複雑さまで成長した場合、

理由もなくクラスのインスタンスを作成するよう消費者に要求する
最も一般的な引数の1つは、クラスのコンシューマーが、後でこのインスタンスを使用しないで、この単一のメソッドを呼び出すためのインスタンスを作成することを要求するのはなぜですか?クラスのインスタンスの作成は、ほとんどの言語で非常に安価な操作であるため、速度は問題になりません。消費者に追加のコード行を追加することは、将来的にはるかに保守可能なソリューションの基盤を構築するための低コストです。最後に、インスタンスの作成を避けたい場合は、簡単に再利用できるようにするクラスのシングルトンラッパーを作成するだけです。ただし、これにより、クラスがステートレスであることが必要になります。ステートレスではない場合でも、すべてを処理する静的なラッパーメソッドを作成しながら、長期的にはすべての利点を利用できます。最後に、

絶対的に扱うのはシスだけです
もちろん、静的メソッドの嫌いには例外があります。肥大化へのリスクをもたらさない真のユーティリティクラスは、静的メソッド(System.Convertなど)の優れたケースです。プロジェクトが将来のメンテナンスを必要としない1回限りのものである場合、全体的なアーキテクチャはそれほど重要ではありません-静的か非静的かは重要ではありません-開発速度は重要です。

標準、標準、標準!
インスタンスメソッドを使用しても、静的メソッドの使用が妨げられることはなく、その逆も同様です。差別化の背後に理由があり、それが標準化されている限り。さまざまな実装方法で広がっているビジネスレイヤーを見ることほど悪いことはありません。


Markが言うように、ポリモーフィズム、「戦略」パラメーターとしてメソッドを渡すことができること、およびオプションを構成/設定できることはすべて、インスタンスを使用する理由です。例えば:ListDelimiterまたはDelimiterParserは使用にデリミタ何に/ ..などのブランク/ヌルリストを治療するための方法を、ブラケットのかどうか、リスト、トリム空白に解析されたトークンからかどうか、受け入れるように構成することができ
トーマス・W

4
「システムが成長するにつれて...」あなたはリファクタリングしますか?
ロドニージッツェル2014

3
@ user3667089クラスが論理的に存在するために必要な一般的な引数、私がコンストラクタで渡す引数。これらは通常、ほとんど/すべての方法で使用されます。特定のメソッドで渡すメソッド固有の引数。
Mark S. Rasmussen、2015

1
静的クラスで実際に行っていることは、生の非オブジェクト指向Cに戻ります(ただし、メモリ管理されます)。すべての関数はグローバルスペースにあり、がないためthis、グローバル状態を管理する必要があります。したがって、手続き型プログラミングが好きなら、こうしてください。しかし、OOの構造上の利点の多くを失うことを認めてください。
エンジニア、

1
これらの理由のほとんどは、静的メソッドの使用ではなく、不適切なコード管理に関係しています。F#およびその他の関数型言語では、基本的に静的メソッド(インスタンス状態のない関数)をどこでも使用しているため、これらの問題は発生しません。とは言っても、F#は関数を使用するための優れたパラダイムを提供し(関数はファーストクラスであり、関数はカリー化して部分的に適用できます)、C#よりも実行可能になります。クラスを使用する大きな理由は、C#がそのために構築されているためです。.NET CoreのすべてのDependency Injectionコンストラクトは、depを持つインスタンスクラスを中心に展開します。
ジャスティンJスターク

89

私は静的な方法を好みます。クラスはオブジェクトを表していないため、そのインスタンスを作成しても意味がありません。

メソッドにのみ存在するクラスは、静的なままにしておく必要があります。


19
-1「メソッドに対してのみ存在するクラスは、静的のままにしておく必要があります。」
Rookian、2011

8
@Rookianどうしてそう思わないの?
jjnguy 2011

6
たとえば、リポジトリパターンです。クラスにはメソッドのみが含まれます。あなたのアプローチに従うと、インターフェースを使用することはできません。=>カップリングを増やす
Rookian、2011

1
@Rook、一般的に人々はメソッドのためだけに使用されるクラスを作成するべきではありません。あなたが静的メソッドを与えた例では、良い考えではありません。ただし、単純なユーティリティメソッドの場合は、静的な方法が最適です。
jjnguy 2011

9
絶対に正しい:)「単純なユーティリティメソッド」の条件については、私は完全に同意しますが、あなたは答えに間違ったimoの一般的なルールを作りました。
Rookian、2011

18

関数を実行するためにクラスのインスタンスを作成する理由がない場合は、静的実装を使用します。インスタンスが不要なときに、このクラスのコンシューマーにインスタンスを作成させる理由。


16

状態を保存する必要がない場合オブジェクトは、最初にインスタンス化する必要はありません。パラメータを渡す単一の静的メソッドを使用します。

また、無関係な静的メソッドが数十ある巨大なUtilsクラスに対しても警告します。これは混乱し、急いで扱いにくくなる可能性があります。関連するメソッドが少ないクラスを多く持つことをお勧めします。


6

静的メソッド形式がより良いオプションだと思います。また、クラスを静的にしたので、誤ってクラスのインスタンスを作成することを心配する必要がなくなります。


5

私はここでどのような状況になっているのか本当にわかりませんが、arg1、arg2、またはarg3が属するクラスの1つにメソッドとして配置することを検討します-これらのクラスの1つが方法。


4

提供された情報に基づいて回答するのは難しいと思います。

私の直感は、メソッドが1つだけで、クラスをすぐに破棄する場合は、すべてのパラメーターを受け取る静的クラスにすることです。

もちろん、この1つのメソッドのためだけに単一のクラスを作成する必要がある理由を正確に伝えるのは困難です。ほとんどの人が想定している典型的な「ユーティリティクラス」の状況ですか?または、将来的にさらに増える可能性のあるルールクラスを実装していますか?

たとえば、そのクラスをプラグ可能にします。次に、1つのメソッドのインターフェイスを作成し、すべてのパラメーターをコンストラクターではなくインターフェイスに渡しますが、それを静的にしたくはありません。


3

クラスを静的にできますか?

もしそうなら、私はそれを「ユーティリティ」クラスにして、すべての1関数クラスを入れます。


2
ややそう思わない。私が関わっているプロジェクトでは、Utilitiesクラスに数百の無関係なメソッドが含まれている可能性があります。
ポールトンブリン

3
@Paul:概ね同意。完全に関連のないメソッドが数十(または数百)あるクラスを見るのは嫌です。このアプローチをとる必要がある場合は、少なくともこれらを、関連する小さなユーティリティセット(EG、FooUtilities、BarUtilitiesなど)に分割してください。
ジョンルディ

9
1つのクラス-1つの責任。
Andreas Petersson

3

このメソッドがステートレスで、渡す必要がない場合は、静的として定義するのが最も理にかなっています。メソッドを渡す必要がある場合は、提案されている他のアプローチではなくデリゲートを使用することを検討してください。


3

単純なアプリケーションとinternalヘルパーの場合は、静的メソッドを使用します。コンポーネントを使用するアプリケーションの場合、私はManaged Extensibility Frameworkを気に入っています。これは、私のAPI全体で見つけるパターンを説明するために書いているドキュメントからの抜粋です。

  • サービス
    • I[ServiceName]Serviceインターフェースによって定義されます。
    • インターフェイスタイプごとにエクスポートおよびインポートされます。
    • 単一の実装は、ホストアプリケーションによって提供され、内部および/または拡張機能によって使用されます。
    • サービスインターフェイスのメソッドはスレッドセーフです。

不自然な例として:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

私はコンストラクタですべてを行います。そのようです:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

または

MyClass my_object(arg1, arg2, arg3);

MyClass型のオブジェクト以外のものを返す必要がある場合はどうでしょうか?
トカゲに請求する

13
コンストラクターに実行ロジックを配置することは悪い習慣だと思います。良い開発とは、意味論です。意味的には、コンストラクタは、他のタスクを実行するためではなく、オブジェクトを構築するために存在します。
mstrobl 2008年

請求書、戻り値が必要な場合は、ret vvalueへの参照を渡します。MyClass myObject(arg1、arg2、arg3、retvalue); mstrobl、オブジェクトが必要ない場合、なぜ作成するのですか?そして、このトリックは実際にはいくつかの例であなたを助けることができます。

オブジェクトが状態を持っていない場合、すなわちには、それがインスタンス化されます、varsはないではないどちらもヒープやスタックに-任意の割り当てを生み出します。C ++のオブジェクトは、構造体を指す非表示の最初のパラメーターを持つC関数呼び出しと考えることができます。
mstrobl 2008年

mstrobl、それはステロイドのAC関数のようです。これは、俳優が使用できるプライベート関数を定義できるためです。クラスメンバー変数を使用して、プライベート関数にデータを渡すこともできます。

0

考慮すべきもう1つの重要な問題は、システムがマルチスレッド環境で実行されるかどうか、および静的なメソッドまたは変数を持つことがスレッドセーフかどうかです...

システム状態に注意を払う必要があります。


0

一緒に状況を回避できるかもしれません。あなたが得るようにリファクタリングしてみてくださいarg1.myMethod1(arg2, arg3)。必要に応じて、arg1をarg2またはarg3と交換します。

arg1のクラスを制御できない場合は、それを修飾します。

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

その理由は、OOPでは、データとそのデータを処理するメソッドが一緒に属しているためです。さらに、マークが言及したすべての利点が得られます。


0

クラスのプロパティまたはクラスのインスタンスがコンストラクターまたはメソッドで使用されない場合、メソッドを「静的」パターンのように設計することはお勧めしません。静的メソッドは常に「ヘルプ」の方法で考える必要があります。


0

あなたが何かをしたいだけなのか、それとも何かをして返したいのかに応じて、これを行うことができます:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.