1. 序章

このクイックチュートリアルでは、シングルトンデザインパターンへのプログラミングとJavaでの静的クラスの使用とのいくつかの顕著な違いについて説明します。 両方のコーディング方法を確認し、プログラミングのさまざまな側面に関して比較します。

この記事の終わりまでに、2つのオプションのどちらかを選択するときに正しい決定を下すことができるようになります。

2. 基礎

グラウンドゼロに到達しましょう。 シングルトンはデザインパターンであり、アプリケーションの存続期間中、クラスの単一インスタンスを保証します。 また、そのインスタンスへのグローバルアクセスポイントも提供します。

static –予約済みキーワード–は、インスタンス変数をクラス変数にする修飾子です。 したがって、これらの変数はクラス(任意のオブジェクト)に関連付けられます。 メソッドと一緒に使用すると、クラス名だけでアクセスできるようになります。 最後に、静的ネストされた内部クラスを作成することもできます。

このコンテキストでは、静的クラスには静的メソッドと静的変数が含まれます

3. シングルトンと静的ユーティリティクラス

それでは、ウサギの穴を掘り下げて、2人の巨人のいくつかの顕著な違いを理解しましょう。 私たちは、いくつかのオブジェクト指向の概念から探求を始めます。

3.1. ランタイムポリモーフィズム

Javaの静的メソッドはコンパイル時に解決され、実行時にオーバーライドすることはできません。 したがって、静的クラスは実行時のポリモーフィズムから真に恩恵を受けることはできません。

public class SuperUtility {

    public static String echoIt(String data) {
        return "SUPER";
    }
}

public class SubUtility extends SuperUtility {

    public static String echoIt(String data) {
        return data;
    }
}

@Test
public void whenStaticUtilClassInheritance_thenOverridingFails() {
    SuperUtility superUtility = new SubUtility();
    Assert.assertNotEquals("ECHO", superUtility.echoIt("ECHO"));
    Assert.assertEquals("SUPER", superUtility.echoIt("ECHO"));
}

対照的に、シングルトンは、基本クラスから派生することにより、他のクラスと同じようにランタイムポリモーフィズムを活用できます。

public class MyLock {

    protected String takeLock(int locks) {
        return "Taken Specific Lock";
    }
}

public class SingletonLock extends MyLock {

    // private constructor and getInstance method 

    @Override
    public String takeLock(int locks) {
        return "Taken Singleton Lock";
    }
}

@Test
public void whenSingletonDerivesBaseClass_thenRuntimePolymorphism() {
    MyLock myLock = new MyLock();
    Assert.assertEquals("Taken Specific Lock", myLock.takeLock(10));
    myLock = SingletonLock.getInstance();
    Assert.assertEquals("Taken Singleton Lock", myLock.takeLock(10));
}

さらに、シングルトンはインターフェイスを実装することもでき、静的クラスよりも優れています。

public class FileSystemSingleton implements SingletonInterface {

    // private constructor and getInstance method

    @Override
    public String describeMe() {
        return "File System Responsibilities";
    }
}

public class CachingSingleton implements SingletonInterface {

    // private constructor and getInstance method

    @Override
    public String describeMe() {
        return "Caching Responsibilities";
    }
}

@Test
public void whenSingletonImplementsInterface_thenRuntimePolymorphism() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    Assert.assertEquals("File System Responsibilities", singleton.describeMe());
    singleton = CachingSingleton.getInstance();
    Assert.assertEquals("Caching Responsibilities", singleton.describeMe());
}

インターフェイスを実装するシングルトンスコープのSpringBeansは、このパラダイムの完璧な例です。

3.2. メソッドパラメータ

これは本質的にオブジェクトであるため、引数としてシングルトンを他のメソッドに簡単に渡すことができます。

@Test
public void whenSingleton_thenPassAsArguments() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    Assert.assertEquals("Taken Singleton Lock", singleton.passOnLocks(SingletonLock.getInstance()));
}

ただし、静的ユーティリティクラスオブジェクトを作成してメソッドに渡すことは無意味であり、悪い考えです。

3.3. オブジェクトの状態、シリアル化、およびクローン可能性

シングルトンはインスタンス変数を持つことができ、他のオブジェクトと同様に、それらの変数の状態を維持できます。

@Test
public void whenSingleton_thenAllowState() {
    SingletonInterface singleton = FileSystemSingleton.getInstance();
    IntStream.range(0, 5)
        .forEach(i -> singleton.increment());
    Assert.assertEquals(5, ((FileSystemSingleton) singleton).getFilesWritten());
}

さらに、シングルトンをシリアル化して、その状態を保持したり、ネットワークなどのメディアを介して転送したりできます。

new ObjectOutputStream(baos).writeObject(singleton);
SerializableSingleton singletonNew = (SerializableSingleton) new ObjectInputStream
   (new ByteArrayInputStream(baos.toByteArray())).readObject();

最後に、インスタンスの存在は、オブジェクトのcloneメソッドを使用してcloneする可能性も設定します。

@Test
public void whenSingleton_thenAllowCloneable() {
    Assert.assertEquals(2, ((SerializableCloneableSingleton) singleton.cloneObject()).getState());
}

逆に、静的クラスにはクラス変数と静的メソッドしかないため、オブジェクト固有の状態はありません。 静的メンバーはクラスに属しているため、シリアル化することはできません。 また、クローンを作成するオブジェクトがないため、静的クラスのクローンは無意味です。 

3.4. 読み込みメカニズムとメモリ割り当て

シングルトンは、クラスの他のインスタンスと同様に、ヒープ上に存在します。 その利点として、アプリケーションで必要な場合はいつでも、巨大なシングルトンオブジェクトを遅延ロードできます。

一方、静的クラスは、コンパイル時に静的メソッドと静的にバインドされた変数を含み、スタックに割り当てられます。 したがって、静的クラスは、JVMでのクラスのロード時に常に熱心にロードされます。

3.5. 効率とパフォーマンス

前に繰り返したように、静的クラスはオブジェクトの初期化を必要としません。 これにより、オブジェクトの作成に必要な時間のオーバーヘッドがなくなります。

さらに、コンパイル時の静的バインディングにより、シングルトンよりも効率的であり、高速になる傾向があります。

シングルトンは設計上の理由でのみ選択する必要があり、効率やパフォーマンスの向上のためのシングルインスタンスソリューションとしては選択しないでください。

3.6. その他の小さな違い

静的クラスではなくシングルトンにプログラミングすることも、必要なリファクタリングの量にメリットがあります。

間違いなく、シングルトンはクラスのオブジェクトです。 したがって、そこからクラスのマルチインスタンスの世界に簡単に移動できます。

静的メソッドはオブジェクトなしでクラス名を使用して呼び出されるため、マルチインスタンス環境への移行は比較的大きなリファクタリングになる可能性があります。

第2に、静的メソッドでは、ロジックがオブジェクトではなくクラス定義に結合されているため、単体テスト対象のオブジェクトからの静的メソッド呼び出しをモックしたり、ダミーやスタブの実装で上書きしたりすることが難しくなります。

4. 正しい選択をする

次の場合はシングルトンを選択します。

  • アプリケーションに完全なオブジェクト指向ソリューションが必要
  • 常にクラスのインスタンスを1つだけ必要とし、状態を維持する必要があります
  • 必要な場合にのみ読み込まれるように、クラスの遅延読み込みソリューションが必要

次の場合に静的クラスを使用します。

  • 入力パラメータのみを操作し、内部状態を変更しない多くの静的ユーティリティメソッドを保存する必要があります
  • ランタイムポリモーフィズムやオブジェクト指向ソリューションは必要ありません

5. 結論

この記事では、静的クラスとJavaのシングルトンパターンの本質的な違いのいくつかを確認しました。 また、ソフトウェアの開発で2つのアプローチのいずれかをいつ使用するかを推測しました。

いつものように、完全なコードはGitHubにあります。