「ユーティリティ関数」クラスを使いこなす


15

Javaコードベースでは、次のパターンを見続けています。

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

気になるのは、FooUtilテストのために、どこでもインスタンスを渡す必要があることです。

  • 作れない FooUtilのメソッドを静的に。なぜなら、両方FooUtilとそのクライアントクラスのテストのためにメソッドをモックできないからです。
  • 私はのインスタンスを作成することはできませんFooUtilと消費の場所でnew、私はテストのためにそれを模擬することができなくなりますので、もう一度、。

私の最善の策は注射を使用することだと思いますが(そして私はそうします)、それはそれ自身の手間を追加します。また、いくつかのutilインスタンスを渡すと、メソッドパラメータリストのサイズが大きくなります。

私が見ていないことをよりうまく処理する方法はありますか?

更新:ユーティリティクラスはステートレスであるため、テスト用にクラスの基になる静的フィールドを更新する機能を保持しながらINSTANCE、おそらく静的シングルトンメンバーまたは静的getInstance()メソッドを追加できます。あまりきれいでもないようです。


4
そして、ユニットテストを効果的に行うためにすべての依存関係をあざける必要があるというあなたの仮定は正しくありません(私はまだそれについて議論している質問を探しています)。
テラスティン

4
これらのメソッドが、データを操作するクラスのメンバーではないのはなぜですか?
ジュール

3
FooUtilityの場合、静的fnsを呼び出すJunitの別個のセットがあります。その後、モックなしで使用できます。モックする必要があると思われる場合、単なるユーティリティではなく、IOまたはビジネスロジックを実行していると思われ、実際にはクラスメンバー参照であり、コンストラクタ、initまたはsetterメソッドを介して初期化する必要があります。
tgkprog

2
ジュールのコメントに対して+1。Utilクラスはコードの匂いです。セマンティクスに応じて、より良いソリューションが必要です。多分FooUtil方法が投入されなければならないFooSomething、多分の性質があるFooSomethingことを変更する必要があります/拡張するようFooUtilな方法はもはや必要ありません。FooSomethingこの質問に対する適切な回答を提供するのに役立つもののより具体的な例を教えてください。
バレンテリー

13
@valenterry:utilクラスがコードの匂いである唯一の理由は、Javaがスタンドアロン関数を持つ良い方法を提供していないからです。クラス固有ではない関数を使用する非常に良い理由があります。
ロバートハーベイ

回答:


16

まず第一に、さまざまな問題に対してさまざまなプログラミング手法が存在するということです。私の仕事では、組織化と保守のために強力なオブジェクト指向設計を好み、私の答えはこれを反映しています。機能的なアプローチには、非常に異なる答えがあります。

メソッドが置かれるべきオブジェクトが存在しないため、ほとんどのユーティリティクラスが作成されることに気付く傾向があります。これはほとんどの場合、誰かがコレクションを渡したり、誰かがBeanを渡したりする2つのケースのいずれかから生じます(これらはしばしばPOJOと呼ばれますが、セッターとゲッターの束はBeanとして最もよく説明されると思います)。

渡すコレクションがある場合、そのコレクションの一部になりたいコードが存在する必要があり、これは最終的にシステム全体に散らばり、最終的にユーティリティクラスに収集されます。

私の提案は、コレクション(またはBean)を他の密接に関連するデータでラップして新しいクラスにし、「ユーティリティ」コードを実際のオブジェクトに配置することです。

コレクションを拡張してそこにメソッドを追加することはできますが、そのようにオブジェクトの状態を制御できなくなります。完全にカプセル化し、必要なビジネスロジックメソッドのみを公開することをお勧めします(「オブジェクトにデータを要求しないでください。重要なオブジェクト指向テナントであり、考えてみると、ここで提案しているアプローチが必要なオブジェクトです。

この「ラッピング」は、データを保持する汎用ライブラリオブジェクトに役立ちますが、コードを追加することはできません。操作するデータでコードを置くことは、オブジェクト指向設計のもう1つの主要な概念です。

このラッピングコンセプトが悪いアイデアになるコーディングスタイルがたくさんあります。1回限りのスクリプトやテストを実装するだけでクラス全体を書きたい人はいますか?ただし、オブジェクト指向では、自分でコードを追加できない基本的なタイプ/コレクションのカプセル化が必須だと思います。


この答えはとても素晴らしいです。私はこの問題を抱えていました(明らかに)、この答えを少し怪しげに読み、戻って自分のコードを見て、「これらのメソッドを持たなければならないオブジェクトが何なのか」と尋ねました。目を見張る、エレガントなソリューションが見つかり、オブジェクトモデルのギャップが埋められました。
GreenAsJade


@Deduplicatorプログラミングの40年で、(インターフェイスではなく実装にジャンプするためのIDEとの時折の戦いは別として)あまりにも多くのレベルの間接参照によってブロックされるケースはめったにありませんでした。しばしば(非常に頻繁に)明らかに必要なクラスを作成するのではなく、人々が恐ろしいコードを書いたケースを見ました。私はあまり間接は一部の人をかましている可能性があると信じていますが、私は、なども一つのことをやって、各クラス、適切な封じ込め、として適切なOOの原則の側でエラーしたい
ビル・K

@BillK一般に、良いアイデアです。しかし、エスケープハッチがなければ、カプセル化は本当に邪魔になります。また、厳密なオブジェクト指向言語では確かにオブジェクト指向以外のものは何でも、あなたが望む任意の型にマッチできる自由なアルゴリズムには特定の美しさがあります。
デュプリケータ

@Deduplicator特定のビジネス要件の外側で記述しなければならない「ユーティリティ」関数を記述するとき、あなたは正しいです。関数(コレクションなど)でいっぱいのユーティリティクラスは、データに関連付けられていないため、オブジェクト指向では単純に扱いにくいです。これらは、オブジェクト指向に慣れていない人々が使用する「エスケープハッチ」です(また、ツールベンダーは非常に汎用的な機能のためにこれらを使用する必要があります)。上記の私の提案は、ビジネスロジックで作業しているときにこれらのハッキングをクラスにラップして、コードをデータに再関連付けできるようにすることでした。
ビルK

1

Providerパターンを使用FooSomethingして、FooUtilProviderオブジェクトを挿入できます(コンストラクターに含めることができます。1 回のみ)。

FooUtilProviderメソッドは1つのみですFooUtils get();。クラスは何でも使用しますFooUtilsそれが提供するインスタンスをします。

DIコインテイナーを2回配線するだけでよい場合、依存性注入フレームワークの使用から1ステップ離れます。実動コード用とテストスイート用です。あなただけのバインドFooUtilsのいずれかへのインタフェースRealFooUtilsMockedFooUtils、残りは自動的に行われます。に依存するすべてのオブジェクトFooUtilsProviderは、の適切なバージョンを取得しますFooUtils

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