Javaコンストラクターと静的ファクトリーメソッド
1. 概要
Javaコンストラクターは、完全に初期化されたクラスインスタンスを取得するためのデフォルトのメカニズムです。 結局、依存関係を注入するために必要なすべてのインフラストラクチャを手動または自動で提供します。
それでも、いくつかの特定のユースケースでは、同じ結果を達成するために静的なファクトリメソッドに頼ることが望ましいです。
このチュートリアルでは、pros and cons of using static factory methods vs plain old Java constructorsを強調表示します。
2. の利点 コンストラクターに対する静的ファクトリメソッド
Javaのようなオブジェクト指向言語では、コンストラクターで何が問題になる可能性がありますか? 全体として、何もありません。 それでも、有名なJoshua Block’s Effective Java Item 1は次のように明確に述べています。
「コンストラクターではなく静的ファクトリメソッドを検討してください」
これは特効薬ではありませんが、このアプローチを維持する最も説得力のある理由は次のとおりです。
-
Constructors don’t have meaningful namesであるため、言語によって課される標準の命名規則に常に制限されます。 Static factory methods can have meaningful names、したがって、それらが何をするかを明示的に伝える
-
Static factory methods can return the same type that implements the method(s), a subtype, and also primitivesなので、より柔軟な範囲の戻り型を提供します
-
Static factory methods can encapsulate all the logic required for pre-constructing fully initialized instancesなので、この追加ロジックをコンストラクターから移動するために使用できます。 これにより、コンストラクターがperforming further tasks, others than just initializing fieldsになるのを防ぎます
-
Static factory methods can be controlled-instanced methods、Singleton patternはこの機能の最も明白な例です
3. JDKの静的ファクトリメソッド
JDKには、上記で概説した多くの利点を示す静的ファクトリメソッドの例がたくさんあります。 それらのいくつかを調べてみましょう。
3.1. Stringクラス
よく知られているString interningのため、Stringクラスコンストラクターを使用して新しいStringオブジェクトを作成することはほとんどありません。 それでも、これは完全に合法です:
String value = new String("example");
この場合、コンストラクターは新しいStringオブジェクトを作成します。これは、予想される動作です。
または、create a new String object using a static factory methodが必要な場合は、valueOf()メソッドの次の実装のいくつかを使用できます。
String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');
valueOf()のオーバーロードされた実装がいくつかあります。 メソッドに渡される引数のタイプに応じて、それぞれが新しいStringオブジェクトを返します(例: int、long、boolean、char,など)。
この名前は、メソッドが実行することを非常に明確に表しています。 また、静的ファクトリメソッドの名前付けに関して、Javaエコシステムで確立された標準に準拠しています。
3.2. Optionalクラス
JDKの静的ファクトリメソッドのもう1つの優れた例は、Optionalクラスです。 このクラスimplements a few factory methods with pretty meaningful names(empty()、of()、ofNullable()を含む):
Optional value1 = Optional.empty();
Optional value2 = Optional.of("example");
Optional value3 = Optional.ofNullable(null);
3.3. Collectionsクラス
おそらくthe most representative example of static factory methods in the JDK is the Collections class.これは、静的メソッドのみを実装するインスタンス化できないクラスです。
これらの多くは、提供されたコレクションに何らかのタイプのアルゴリズムを適用した後、コレクションを返すファクトリメソッドです。
クラスのファクトリメソッドの典型的な例を次に示します。
Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List unmodifiableList = Collections.unmodifiableList(originalList);
Map unmodifiableMap = Collections.unmodifiableMap(originalMap);
JDKの静的ファクトリメソッドの数は非常に多いため、簡潔にするために例のリストは短くしておきます。
それでも、上記の例から、Javaでのユビキタスな静的ファクトリメソッドの明確なアイデアが得られるはずです。
4. カスタム静的ファクトリメソッド
もちろん、we can implement our own static factory methods.ですが、プレーンコンストラクタを介してクラスインスタンスを作成する代わりに、そうする価値があるのはいつですか?
簡単な例を見てみましょう。
この素朴なUserクラスについて考えてみましょう。
public class User {
private final String name;
private final String email;
private final String country;
public User(String name, String email, String country) {
this.name = name;
this.email = email;
this.country = country;
}
// standard getters / toString
}
この場合、静的ファクトリメソッドが標準のコンストラクタよりも優れている可能性があることを示す目に見える警告はありません。
すべてのUserインスタンスがcountryフィールドのデフォルト値を取得するようにするにはどうすればよいですか?
フィールドをデフォルト値で初期化する場合、コンストラクターもリファクタリングする必要があるため、設計がより厳密になります。
代わりに静的ファクトリーメソッドを使用できます。
public static User createWithDefaultCountry(String name, String email) {
return new User(name, email, "Argentina");
}
countryフィールドにデフォルト値が割り当てられたUserインスタンスを取得する方法は次のとおりです。
User user = User.createWithDefaultCountry("John", "[email protected]");
5. コンストラクターからのロジックの移動
コンストラクターにロジックを追加する必要がある機能を実装することにした場合、Userクラスはすぐに欠陥のある設計になってしまう可能性があります(この時点でアラームベルが鳴るはずです)。
すべてのUserオブジェクトが作成された時刻をログに記録する機能をクラスに提供するとします。
If we just put this logic into the constructor, we’d be breaking the Single Responsibility Principle。 フィールドを初期化する以上のことを行うモノリシックコンストラクターになります。
静的ファクトリメソッドを使用して、設計をクリーンに保つことができます。
public class User {
private static final Logger LOGGER = Logger.getLogger(User.class.getName());
private final String name;
private final String email;
private final String country;
// standard constructors / getters
public static User createWithLoggedInstantiationTime(
String name, String email, String country) {
setLoggerProperties();
LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
return new User(name, email, country);
}
private static void setLoggerProperties() {
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.INFO);
handler.setFormatter(new SimpleFormatter());
LOGGER.addHandler(handler);
}
}
改善されたUserインスタンスを作成する方法は次のとおりです。
User user
= User.createWithLoggedInstantiationTime("John", "[email protected]", "Argentina");
6. インスタンス制御のインスタンス化
上に示したように、完全に初期化されたUserオブジェクトを返す前に、ロジックのチャンクを静的ファクトリメソッドにカプセル化できます。 そして、関連のない複数のタスクを実行する責任をコンストラクタに与えずにこれを行うことができます。
たとえば、suppose we want to make our User class a Singleton. We can achieve this by implementing an instance-controlled static factory method:
public class User {
private static volatile User instance = null;
// other fields / standard constructors / getters
public static User getSingletonInstance(String name, String email, String country) {
if (instance == null) {
synchronized (User.class) {
if (instance == null) {
instance = new User(name, email, country);
}
}
}
return instance;
}
}
getSingletonInstance()メソッドの実装はthread-safe, with a small performance penalty, due to the synchronized blockです。
この例では、遅延初期化を使用して、インスタンス制御の静的ファクトリメソッドの実装を示しました。
ただし、the best way to implement a Singleton is with a Java enum type, as it’s both serialization-safe and thread-safeについては言及する価値があります。 さまざまなアプローチを使用してシングルトンを実装する方法の詳細については、this articleを確認してください。
予想どおり、このメソッドでUserオブジェクトを取得することは、前の例と非常によく似ています。
User user = User.getSingletonInstance("John", "[email protected]", "Argentina");
7. 結論
この記事では、プレーンなJavaコンストラクターを使用する代わりに、静的ファクトリーメソッドを使用する方が優れているいくつかのユースケースを検討しました。
さらに、このリファクタリングパターンは、ほとんどのIDEがそれを行う典型的なワークフローに非常に密接に根ざしています。
もちろん、Apache NetBeans、IntelliJ IDEA、およびEclipseは、わずかに異なる方法でリファクタリングを実行するため、最初にIDEのドキュメントを確認してください。
他の多くのリファクタリングパターンと同様に、静的ファクトリメソッドを慎重に使用する必要があります。これは、より柔軟でクリーンなデザインを作成することと、追加のメソッドを実装するコストとの間のトレードオフに値する場合に限ります。
いつものように、この記事に示されているすべてのコードサンプルは利用可能なover on GitHubです。