2017年10月7日土曜日

3: スタティックメンバークラスを使うべきところとシングルトンを使うべきところ

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>

本文 START

スタティックメンバークラスではだめなとき、シングルトンが使える

話題: Javaプログラミング言語

クラス毎に同時にはただ1つの状態だけが必要な場合、スタティックメンバークラスは便利だ

-Hypothesizer

クラス毎に同時にはただ1つの状態だけが必要な場合、スタティックメンバークラスはしばしば便利だ

-Rebutter

「スタティックメンバークラス」というのは何のことだ?

-Hypothesizer

スタティックフィールドとスタティックメソッドだけを持つクラスのことだ、以下のように。

@Java Source Code
public class Test1StaticMembersClass {
 private static String s_valueA = "initial value A";
 
 private Test1StaticMembersClass () {
 }
 
 public static void setValueA (String a_valueA) {
  s_valueA = a_valueA;
 }
 
 public static void printValueA () {
  System.out.println (String.format ("The value A is \"%s\".", s_valueA));
 }
}
-Rebutter

ネストされたクラスのことではないわけだ。

-Hypothesizer

そう。

スタティックメンバークラスは、クラスインスタンスを生成する必要がなく、以下のように使える。

@Java Source Code
public class Test1TestClass {
 public void test () {
  Test1StaticMembersClass.setValueA ("changed value A");
  Test1StaticMembersClass.printValueA ();
 }
}
-Rebutter

スタティックメンバークラスは、同時にはただ1つの状態だけを持つが、状態は経時的に変更することができる。

-Hypothesizer

システム全体が、どの時点においても、正しいただ1つの状態を見ることが重要だ。

値が不変でよいのであれば、インスタンスフィールドを使って、以下のようにすることもできる。

@Java Source Code
public class Test2InstanceMembersClass {
 private String i_valueA = "constant value A";
 
 public void printValueA () {
  System.out.println (String.format ("The value A is \"%s\".", i_valueA));
 }
}

public class Test2TestClass {
 public void test () {
  (new Test2InstanceMembersClass ()).printValueA ();
 }
}
-Rebutter

'Test2InstanceMembersClass'のインスタンスを複数生成することができるが、どのインスタンスの値も同じ単一の値であることが保証されている。フィールドがプライベートでセッターメソッドがないから。しかし、値を変えることはできない。

-Hypothesizer

それに、スタティックメンバークラスでは、値のインスタンスが1つだけであるという恩恵があり、これはメモリスペースを無駄にしないということを意味する。

-Rebutter

上の例では、メモリスペースの無駄は実質上ゼロであるように思えるが、状態が大きなメモリスペースを占めるという場合もあり得る。

-Hypothesizer

それに、状態の初期化が多量のCPUリソースを必要とするかもしれない。

-Rebutter

かもしれない。

-Hypothesizer

これらの恩恵を、フィールドをスタティックであると宣言するだけで得られる。

スタティックメンバークラスを拡張するとき気をつけるべきこと

-Hypothesizer

しかし、スタティックメンバークラスを拡張したいとき、我々はジレンマに直面するかもしれない。例えば、サブクラスでフィールドの値を上書きしたいかもしれない。

-Rebutter

たぶんそれは、別のサブクラスには別の値を持たせたいということだろう?

-Hypothesizer

そうだ。だから、同じ1つのスーパークラスフィールドをサブクラス間で共有するのは意味がなく、以下のように、いわゆる、シャドーイングをしなければならないだろう。

@Java Source Code
public abstract class Test3BaseStaticMembersClass {
 protected static String s_valueA = "initial value A";
 
 public static void setValueA (String a_valueA) {
  s_valueA = a_valueA;
 }
 
 public static void printValueA () {
  System.out.println (String.format ("The value A is \"%s\".", s_valueA));
 }
}

public class Test3ExtendedStaticMembersClass extends Test3BaseStaticMembersClass {
 protected static String s_valueA = "overwritten value A";
 
 private Test3ExtendedStaticMembersClass () {
 }
}
-Rebutter

ははあ . . .

-Hypothesizer

しかし、これを以下のように使うと、結果は私が望むものではない。

@Java Source Code
public class Test3TestClass {
 public void test () {
  Test3ExtendedStaticMembersClass.printValueA ();
 }
}
-Rebutter

クラスメンバーアクセス解決のルールを考えれば、そうなるだろう。

スタティックメンバーに対するクラスメンバーアクセス解決のルールを復習しよう

-Hypothesizer

我々は、ある表現によって、どのクラスメンバーがアクセスされるかを決定するルールについて話している。

-Rebutter

そうだ。例えば、上の'printValueA'内の表現、's_valueA'によって、'Test3BaseStaticMembersClass'の's_valueA'がアクセスされるのか、'Test3ExtendedStaticMembersClass'の's_valueA'がアクセスされるのか?

-Hypothesizer

ここで、クラスメンバーアクセス解決のルールを徹底的に復習しようと思ったのだが、どうも、長すぎる脱線になってしまうようだ。そこで、ここでは、スタティックメンバーに対してのみのクラスメンバーアクセス解決のルールを復習しよう(任意のメンバーに対してのルールは、将来の記事で復習する)。

-Rebutter

いいだろう。

-Hypothesizer

第1に、表現中の、メンバー名への修飾を見なければならない。

-Rebutter

'修飾'というのは何のことだ?

-Hypothesizer

表現が'Test3ExtendedStaticMembersClass.s_valueA'であるとき、'Test3ExtendedStaticMembersClass'がメンバー名、's_valueA'への修飾だ。

単に、's_valueA'と言ったのでは、コンパイラもランタイムもどの's_valueA'なのかを判断できない。だから、常に、何らかの修飾がなければならない。

-Rebutter

上のコードでは、表現は単に's_valueA'であって、何の修飾もない。

-Hypothesizer

その場合、我々は、暗黙の修飾があると考える。暗黙の修飾を明るみに出すことで、ルールを合理的に理解できる。

-Rebutter

オーケー。それでは、第1に、我々は、表現中の、メンバー名への修飾を見る、それが明示的であろうが、暗黙的であろうが。それからどうする?

-Hypothesizer

もしも、修飾のクラスがそのメンバー名のスタティックメンバーを持っていれば、そのメンバーがアクセスされる。そうでなければ、それはここで取り扱っている範疇にはない。インスタンスメンバーがアクセスされるか、コンパイルエラーが起きるだろうから。

-Rebutter

「修飾のクラス」というのはどういう意味なのか?

-Hypothesizer

修飾がクラスの場合、その、クラスそのものがそれだ。

-Rebutter

そうだと思った。

-Hypothesizer

修飾が変数の場合、変数タイプがそれだ。

-Rebutter

以下の例を考えてみよう。

@Java Source Code
  Test3BaseStaticMembersClass l_object = new Test3ExtendedStaticMembersClass ();

変数'l_object'にとって、'Test3BaseStaticMembersClass'が( 'Test3ExtendedStaticMembersClass'ではなく)、変数タイプだ。

-Hypothesizer

そう。我々は、'Test3ExtendedStaticMembersClass'のことをインスタンスタイプと呼ぶ。

-Rebutter

いいだろう。

-Hypothesizer

修飾が表現の場合、表現タイプがそれだ。例えば、修飾'( (Test3BaseStaticMembersClass) new Test3ExtendedStaticMembersClass ())'において、'Test3BaseStaticMembersClass'がそれだ。

-Rebutter

ははあ。すると、修飾のクラスはインスタンスタイプのことではないことを知ることが重要だ。

-Hypothesizer

そう。

短く言えば、修飾の表現タイプが修飾のクラスだと言えるだろう。どんなクラスや変数も一種の表現だから。

-Rebutter

すると、残っているのは、「暗黙の修飾が何か?」ということだ。

-Hypothesizer

表現がスタティックコンテキストの中にある場合、暗黙の修飾は、表現が埋め込まれているクラスだ。表現がインスタンスコンテキストの中にある場合、暗黙の修飾は、'this'だ。

-Rebutter

スタティックメソッドの中は典型的なスタティックコンテキストであり、インスタンスメソッドの中は典型的なインスタンスコンテキストだな?

-Hypothesizer

そうだ。別の言い方では、'this'が使える任意の場所がインスタンスコンテキストで、他のところがスタティックコンテキストだ。

-Rebutter

オーケー。

-Hypothesizer

したがって、上の'printValue'内の表現、's_valueA'がどのクラスメンバーに解決されるかを知るには、我々は、第1に、修飾が暗黙であることを認識し、次に、表現がスタティックメソッド内にあるので、コンテキストがスタティックでであることを認識する。そこで、我々は、修飾が、表現が埋め込まれているクラスである'Test3BaseStaticMembersClass'であることを理解し、修飾のクラスが'Test3BaseStaticMembersClass'であることを理解する。最後に、我々は、'Test3BaseStaticMembersClass'が、指定された名前のスタティックメンバーを持っていることを認識する。

-Rebutter

だから、'Test3BaseStaticMembersClass'の's_valueA'がアクセスされる、'Test3ExtendedStaticMembersClass'のではなく。

-Hypothesizer

「1文か数文で簡単に説明できないのか?」といった苦情が予測できる。まあ、できなくはない、もしも、一部の例外を隠す直感的な説明をしようとするならば。私は、どんな例外もない、常に真である説明をしようとした。

-Rebutter

まあいいだろう。

それでは、スタティックメンバークラスをどうすればよいのか

-Hypothesizer

それでは、'test3'のケースをどうすればよいのだろうか?まあ、1つの方法は、サブクラスで'printValueA'を再定義することだろう。

-Rebutter

スーパークラスの'printValueA'の全く同じコピーを定義しないといけないだろう。それは、行儀の悪いコードに思える。'printValueA'を変更したいときは、すべてのコピーを変更しなければならなくなる。

-Hypothesizer

分かっている。. . . まあ、重要な部分を別のメソッドに分離することはできる、以下のように。

@Java Source Code
public abstract class Test4BaseStaticMembersClass {
 private static String s_valueA = "initial value A";
 
 protected static void printSpecifiedValue (String a_valueName, String a_value) {
  System.out.println (String.format ("The value %s is \"%s\".", a_valueName, a_value));
 }
 
 public static void printValueA () {
  printSpecifiedValue ("A", s_valueA);
 }
}

public class Test4ExtendedStaticMembersClass extends Test4BaseStaticMembersClass {
 private static String s_valueA = "overwritten value A";
 
 private Test4ExtendedStaticMembersClass () {
 }
 
 public static void printValueA () {
  printSpecifiedValue ("A", s_valueA);
 }
}

public class Test4TestClass {
 public void test () {
  Test4ExtendedStaticMembersClass.printValueA ();
 }
}
-Rebutter

ましには見えるが、それでも、すべてのサブクラスで'printValueA'の同一のコピーを作らなければならないのは面倒に思える。

どういう場合に注意すべきか?

-Hypothesizer

結局、メンバーを上書きする場合、スタティックメンバークラスは最適とはいえない。

-Rebutter

それは、不正確な表現だ。上書きされるメンバーがスーパークラス内で参照されて(たいていはメソッドから参照される)いなければ、何の問題もないだろう。

-Hypothesizer

ああ、もしも、's_valueA'が'printValueA'からアクセスされていなければ、シャドーイングをしても、何の問題もないだろう。すると、スーパークラスで参照されているメンバーを上書きする場合、スタティックメンバークラスは最適とはいえない。

-Rebutter

そのように思われる。

シングルトンがある

-Hypothesizer

それでは、どうできるのか?

-Rebutter

クラス毎にただ1つの状態を維持するということだけが関心事ならば、別に、スタティックメンバークラスを使うことに固執する必要はない。

-Hypothesizer

それでは何を使うのか?. . . ああ、そうか、シングルトンがある。

-Rebutter

もちろん、ある。

-Hypothesizer

以下のようにする。

@Java Source Code
public abstract class Test5BaseSingletonClass {
 private String i_valueA = "initial value A";
 
 public void setValueA (String a_valueA) {
  i_valueA = a_valueA;
 }
 
 public void printValueA () {
  System.out.println (String.format ("The value A is \"%s\".", i_valueA));
 }
}

public class Test5ExtendedSingletonClass extends Test5BaseSingletonClass {
 public static final Test5ExtendedSingletonClass instance = new Test5ExtendedSingletonClass ();
 
 private Test5ExtendedSingletonClass () {
  setValueA ("overwritten value A");
 }
}

public class Test5TestClass {
 public void test () {
  Test5ExtendedSingletonClass.instance.printValueA ();
 }
}
-Rebutter

こうすれば、シャドーイングをする必要もなく、メソッドをコピーする必要もない。

-Hypothesizer

サブクラスは、自身の単一インスタンスを生成してパブリックスタティックファイナルフィールドとして公開し、コンストラクタはプライベートにして、そのインスタンスのそれ以上の生成を禁止する。

本文 END

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>