技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

続・singletonとstaticの違い

こちらの記事について社内外でちょっとした議論になったので、その内容をまとめてみました。
Singletonパターンを利用する理由は「外部からnewさせたくないから」だと思っていましたが、「そう書いた方がわかりやすいから」「staticではないメソッドも利用可能になるから」という理由の方が大きそうです。
 
【指摘】
・privateコンストラクタを持った時点でnewできない
 newさせたくないだけなら、オブジェクト取得メソッドは不要。
 Singletonパターンで無くても良い。
 
・Singletonパターンの思想の表現としてオブジェクト取得メソッドが必要
 privateでオブジェクトを1つ生成、外部にはメソッド経由で提供する、とすれば、
 「オブジェクトは1つのみ存在する」という思想をわかりやすく表現できる。
 書籍ではSingletonパターンはオブジェクト取得メソッドが必要とされている。
 流行っている書き方だから他の開発者にも理解しやすい、というのはありそう。
 
・Singletonパターンだとstaticではないメソッドも参照可能になる
 Singletonパターンであればクラスへの参照ではなくオブジェクトへの参照となる。
 そのため、staticではないメソッドの参照も可能となる。
 具体的には、継承・オーバーライドができるというメリットがある。
 
【サンプルコード】
・StaticMemory.java
public class StaticMemory {

  // 利用者側でnewさせたくないだけならこれだけで良い
  private StaticMemory() {
  }

  private static int num = 0;

  public static void setNum(int arg) {
    num = arg;
  }

  public static int getNum() {
    return num;
  }

}
 
・SingletonMemory.java
public class SingletonMemory {

  private static int num = 0;
  private static SingletonMemory instance = new SingletonMemory();

  protected SingletonMemory(){
  }

  public static SingletonMemory getInstance(){
    return instance;
  }

  public static void setNum(int arg) {
    num = arg;
  }

  public static int getNum() {
    return num;
  }

  public int getNumCalc() {
    return num * 2;
  }

}
 
・SingletonMemoryChild.java
public class SingletonMemoryChild extends SingletonMemory {

  private static SingletonMemoryChild instance = new SingletonMemoryChild();

  private SingletonMemoryChild(){
  }

  public static SingletonMemoryChild getInstance(){
    return instance;
  }

  // 下記はビルドエラー
  // @Override
  // public static int getNum() {
  //  return 100;
  //}

  @Override
  public int getNumCalc() {
    return super.getNumCalc() * 2;
  }

}
 
・MemoryTestMain.java
public class MemoryTestMain {

  // 要件:最新の値をメモリに保持したい
  @SuppressWarnings("static-access")
  public static void main(String[ ] args) {

    int recentNum = 0; // 最新の値

    // 普通のStaticなクラスを使った場合
    recentNum = 1;
    StaticMemory.setNum(recentNum);
    System.out.println("Static1:"+StaticMemory.getNum());

    // privateコンストラクタを定義すれば使う側でオブジェクトを作れない
    // (下記はエラー)
    // StaticMemory instance2 = new StaticMemory();
    // instance2.setNum(recentNum);
    // System.out.println("new1:"+instance2.getNum());

    // オブジェクト取得メソッドを利用すれば非staticメソッドも参照可能
    recentNum = 2;
    SingletonMemory.getInstance().setNum(recentNum);
    System.out.println
      ("Shingleton1:"+SingletonMemory.getInstance().getNum());
    System.out.println
      ("Shingleton2:"+SingletonMemory.getInstance().getNumCalc());

    // 直接参照だと非staticメソッドを参照できない
    recentNum = 3;
    SingletonMemory.setNum(recentNum);
    System.out.println
      ("直接参照1:"+SingletonMemory.getNum());
    // System.out.println
    //   ("直接参照2:"+SingletonMemory.getNumCalc());

    // Singletoneなクラスを継承したクラスを利用
    // 非staticメソッドはオーバーライドしたメソッドが呼び出される
    recentNum = 4;
    SingletonMemoryChild.getInstance().setNum(recentNum);
    System.out.println
      ("Child1:"+SingletonMemoryChild.getInstance().getNum());
    System.out.println
      ("Child2:"+SingletonMemoryChild.getInstance().getNumCalc());

  }

}
 
【実行結果】
Staticクラス1:1
メソッドでget1:2
メソッドでget2:4
直接参照1:3
メソッドでget1(継承):4
メソッドでget2(継承):16