静的ファクトリーとシングルトンとしてのファクトリー


24

私のコードのいくつかでは、次のような静的ファクトリーがあります。

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

コードのレビュー中に、これはシングルトンであることが提案され、注入されました。したがって、次のようになります。

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

強調すべきいくつかのこと:

  • 両方の工場はステートレスです。
  • メソッド間の唯一の違いは、スコープ(インスタンスと静的)です。実装は同じです。
  • Fooは、インターフェースを持たないBeanです。

静的にするための引数は次のとおりです。

  • クラスはステートレスであるため、インスタンス化する必要はありません
  • ファクトリをインスタンス化するよりも静的メソッドを呼び出す方が自然なようです

シングルトンとしてのファクトリの引数は次のとおりです。

  • すべてを注入するのが良い
  • 工場の無国籍にもかかわらず、注入によりテストが簡単になります(簡単にモックできます)
  • 消費者をテストするとき、それはock笑されるべきです

どのメソッドも静的であってはならないことを示唆しているように見えるため、シングルトンアプローチにはいくつかの深刻な問題があります。また、ユーティリティをStringUtilsラップしてインジェクトする必要があることも示唆しているようですが、これはばかげているようです。最後に、ある時点で工場をモックする必要があることを意味しますが、これは正しくないと思われます。いつ工場をモックする必要があるのか​​考えられません。

コミュニティはどう思いますか?私はシングルトンアプローチが好きではありませんが、それに対してひどく強い議論を持っているようには見えません。


2
工場は何かによって使われています。そのことを単独でテストするには、工場のモックを提供する必要があります。工場のモックを作成しないと、バグがあるとユニットテストが失敗する可能性があります。
マイク

それで、あなたは一般に静的ユーティリティに反対しているのですか、それとも単なる静的ファクトリに反対していますか?
bstempi

1
私は一般的にそれらに反対しています。たとえば、C#では、DateTimeFileクラスはまったく同じ理由でテストするのが難しいことで有名です。たとえば、コンストラクタにCreated日付を設定するクラスがある場合、DateTime.Now5分間隔で作成されたこれらのオブジェクトのうち2つを使用して単体テストを作成するにはどうすればよいですか?何年も離れていますか?あなたは本当にそれをすることはできません(多くの仕事なしで)。
マイク

2
シングルトンファクトリにprivateコンストラクタとgetInstance()メソッドを含めるべきではありませんか?申し訳ありませんが、不可解なnit-picker!
TMN

1
@Mike-同意-私は、DateTime.Nowを自明に返すDateTimeProviderクラスをよく使用しました。その後、テストで事前に決められた時間を返すモックに交換できます。それはすべてのコンストラクタに渡す余分な作業である-同僚がそれまでに100%を購入し、怠惰のうち、DateTime.Nowにして明示的な参照をスリップしていない時に頭痛がある最大の... :)
ジュリア・ヘイワード

回答:


15

作成したオブジェクト型からファクトリを分離するのはなぜですか?

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

これは、ジョシュアブロッホが著書「Effective Java」の5ページにある彼のアイテム1として説明しているものです。Javaは、余分なクラスやシングルトンなどを追加しなくても十分に冗長です。別のファクトリクラスが理にかなっている場合もありますが、それらはほとんどありません。

コンストラクターとは異なり、Joshua BlochのItem 1を言い換えると、静的ファクトリーメソッドは次のことができます。

  • 特定の種類のオブジェクト作成を説明できる名前を付けます(Blochは例としてprobablePrime(int、int、Random)を使用します)
  • 既存のオブジェクトを返します(たとえば、flyweight)
  • 元のクラスのサブタイプまたは実装するインターフェースを返します
  • 冗長性を減らす(型パラメーターを指定する-たとえばBlochを参照)

短所:

  • パブリックコンストラクターまたは保護されたコンストラクターのないクラスはサブクラス化できません(ただし、保護されたコンストラクターおよび/または項目16を使用できます:継承よりも合成を優先します)
  • 静的ファクトリーメソッドは、他の静的メソッドから際立っていません(of()またはvalueOf()などの標準名を使用します)

本当に、Blochの本をコードレビュアーに渡して、2人がBlochのスタイルに同意できるかどうかを確認してください。


私もこれについて考えましたが、私はそれが好きだと思います。最初にそれらを分離した理由を思い出せません。
bstempi

一般的に言えば、Blochのスタイルに同意します。最終的に、ファクトリメソッドを、インスタンス化するクラスに移動します。私たちのソリューションとあなたのソリューションの唯一の違いは、コンストラクタがパブリックのままであるため、人々がクラスを直接インスタンス化できることです。ご回答有難うございます!
bstempi

しかし...それは恐ろしいことです。ほとんどの場合、IFooを実装するクラスが必要で、それを渡します。このパターンを使用すると、それは不可能になります。インスタンスを取得するには、Fooを意味するようにIFooをハードコーディングする必要があります。IFoo create()を含むIFooFactoryがある場合、クライアントコードは、IFooとして選択された具体的なFooの実装の詳細を知る必要はありません。
ウィルバート

@ウィルバートpublic static IFoo of(...) { return new Foo(...); } 問題は何ですか?Blochは、この設計の利点の1つとして、懸念事項を満たします(上記の2番目の箇条書き)。あなたのコメントを理解してはいけません。
グレンペターソン

インスタンスを作成するために、この設計ではFoo.of(...)を想定しています。フーの存在が露出してはいけません(fooがあるが、しかし、唯一のIFooが知られている必要がありIFooの具体的な独自の実装)。具体的な実装に静的なファクトリメソッドがあると、これが壊れ、クライアントコードと具体的な実装が結合されます。
ウィルバート

5

私の頭の中で、Javaの静的に関する問題のいくつかを次に示します。

  • 静的メソッドは、OOPの「ルール」によって再生されません。クラスに特定の静的メソッドを強制的に実装することはできません(私の知る限り)。したがって、同じシグネチャを持つ同じ静的メソッドのセットを実装する複数のクラスを持つことはできません。だから、あなたはそれがやっていることのいずれかで立ち往生しています。静的クラスを実際にサブクラス化することもできません。ポリモーフィズムなどはありません

  • メソッドはオブジェクトではないため、静的メソッドを渡すことはできず、使用することはできません(大量の定型コーディングを行わない限り)。ただし、それらにハードリンクされたコードを除きます。結合。(公平を期すために、これは単に静的な問題ではありません)。

  • 静的フィールドはグローバルにかなり似ています


あなたは言った:

どのメソッドも静的であってはならないことを示唆しているように見えるため、シングルトンアプローチには重大な問題がいくつかあります。また、StringUtilsなどのユーティリティをラップして挿入する必要があることを示唆しているようですが、これはばかげているようです。最後に、ある時点で工場をモックする必要があることを意味しますが、これは正しくないと思われます。いつ工場をモックする必要があるのか​​考えられません。

それで私はあなたに尋ねたいと思うようになります:

  • あなたの意見では、静的メソッドの利点は何ですか?
  • それStringUtilsは正しく設計および実装されていると思いますか?
  • なぜ工場をモックする必要がないと思いますか?

ねえ、答えてくれてありがとう!(1)静的メソッドの利点は、構文糖と不要なインスタンスの欠如です。静的なインポートを持つコードを読みFoo f = createFoo();、名前付きインスタンスを持つよりも、何かを言うのはいいことです。インスタンス自体はユーティリティを提供しません。(2)そう思う。これらのメソッドの多くがStringクラス内に存在しないという事実を克服するために設計されました。Stringオプションではないものと同じくらい基本的なものを置き換えるので、utilsが最適な方法です。(続き)
bstempi

それらは使いやすく、インスタンスを気にする必要はありません。彼らは自然に感じます。(3)私のファクトリは内のファクトリメソッドに非常に似ているためですInteger。それらは非常にシンプルで、ロジックがほとんどなく、利便性を提供するためだけにあります(たとえば、他にオブジェクト指向の理由はありません)。私の議論の主な部分は、状態に依存しないことです。テスト中にそれらを分離する理由はありません。StringUtilsテスト時に人々はモックをしません-なぜこの単純な便利なファクトリーをモックする必要があるのでしょうか?
bstempi

3
StringUtilsそこにはメソッドに副作用がないという合理的な保証があるため、彼らはock笑しません。
ロバートハーベイ

@RobertHarvey私は自分の工場について同じ主張をしていると思います-それは何も変更しません。StringUtils入力を変更せずに結果を返すように、私のファクトリも何も変更しません。
bstempi

@bstempi私はそこで少しソクラテスの方法を探していました。私はそれを吸うと思います。

0

構築しているアプリケーションは、かなり複雑でないか、ライフサイクルの比較的早い段階にあると思います。あなたが静的の問題にまだ遭遇していなかったと私が考えることができる他の唯一のシナリオは、あなたのファクトリについて知っているコードの他の部分を1つか2つの場所に制限することができた場合です。

アプリケーションが進化し、場合によってはFooBar(Fooのサブクラス)を生成する必要があることを想像してください。今、あなたはあなたのことを知っているコード内のすべての場所SomeFactoryを調べてsomeConditionSomeFactoryまたはを見て呼び出したりするロジックを書く必要がありSomeOtherFactoryます。実際、サンプルコードを見ると、それよりも悪いです。さまざまなFoosを作成するためにメソッドを追加するだけで、クライアントコードはすべて、どのFooを作成するかを判断する前に条件をチェックする必要があります。つまり、すべてのクライアントはあなたのFactoryがどのように動作するかを熟知している必要があり、すべてのクライアントは新しいFooが必要(または削除!)されるたびに変更する必要があります。

作成したばかりの場合は今想像しIFooFactoryますことをIFoo。今、異なるサブクラスまたは完全に異なる実装を作成することは子供の遊びです-チェーンの上位の正しいIFooFactoryにフィードし、クライアントがそれgimmeAFoo()を取得すると、適切なFactoryを取得したため、適切なfooを呼び出して取得します。

これが実稼働コードでどのように機能するか疑問に思われるかもしれません。私が取り組んでいるコードベースから1つの例を挙げると、プロジェクトをわずか1年ほどクランクアウトした後、平準化し始めています。質問データオブジェクトを作成するために使用される工場がたくさんあります(プレゼンテーションモデルのようなものです))。私が持っているQuestionFactoryMap基本的には使用時に質問ファクトリを検索するためのハッシュだということを。そこで、さまざまな質問をするアプリケーションを作成するために、さまざまなファクトリーをマップに追加しました。

質問は、指示または演習(両方とも実装IContentBlock)で提示できるため、それぞれInstructionsFactoryまたはExerciseFactoryQuestionFactoryMapのコピーを取得します。次に、さまざまな命令と演習のファクトリが、ContentBlockBuilderいつ使用するかのハッシュを再度保持します。そして、XMLがロードされると、ContentBlockBuilderはハッシュからエクササイズまたはインストラクションファクトリを呼び出してに要求しcreateContent()、次にファクトリは何に基づいて内部QuestionFactoryMapから引き出したものにでも質問の構築を委任します各XMLノードとハッシュにあります。残念ながら、そのことはあるBaseQuestionの代わりに、IQuestion、しかし、それはあなたがデザインに注意しているときでさえ、あなたがラインにあなたを悩ませることができる間違いを犯すことができることを示すことになります。

あなたの腸は、これらの静力学があなたを傷つけることをあなたに告げています、そして確かにあなたの腸をサポートするための文献たくさんあります。ユニットテストにのみ使用する依存関係注入を行うことで決して失われることはありません(良いコードを作成してもそれ以上使用しないと信じているわけではありません)が、静的関数は能力を完全に破壊する可能性がありますコードをすばやく簡単に変更できます。なぜそこに行くのでしょうか?


Integerクラスのファクトリメソッドについて少し考えてみてください。String からの変換のような単純なことです。それがそうFooです。私Fooは拡張されることを意図していません(これを述べていないのは私のせいです)ので、私にはあなたの多態性の問題はありません。私は多形性の工場を持つことの価値を理解し、過去にそれらを使用しました...私はそれらがここで適切であるとは思わない。Integer静的ファクトリーを介して現在焼き付けられている単純な操作を行うために、ファクトリーをインスタンス化する必要がある場合を想像できますか?
bstempi

また、アプリケーションに関する想定はかなりずれています。アプリケーションはかなり複雑です。Fooただし、これは多くのクラスよりもはるかに簡単です。単純なPOJOです。
bstempi

Fooを拡張する場合は、ファクトリがインスタンスになる理由がありますif (this) then {makeThisFoo()} else {makeThatFoo()}。コード全体を呼び出すのではなく、ファクトリの正しいインスタンスを提供して正しいサブクラスを提供します。慢性的に悪いアーキテクチャを持つ人々について私が気づいたことの1つは、彼らが慢性的な苦痛の人々のようだということです。彼らは実際に痛みを感じないことを知らないので、痛みはそれほどひどくはありません。
エイミーブランケンシップ

また、あなたがチェックアウトする場合があります。このリンクの静的メソッドでの問題について(でも数学のものを!)
エイミーブランケン

質問を更新しました。あなたと他の1人が延長に言及しましたFoo。当然のことながら、私はそれを最終的に宣言したことはありません。 Foo拡張されることを意図していないBeanです。
bstempi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.