23種類の設計モード(21)java享元モード


閻宏博士の『JAVAとモード』の冒頭で、享元(Flyweight)モードをこう説明した:
Flyweightはボクシングの試合で一番軽いのを指します。即ち、「ハエ級」または「雨量級」です。ここで「享元モード」を使う意訳を選択します。これはもっとモードの意味を反映するからです。享元モードはオブジェクトの構造モードである。享元モードは多数の微細度オブジェクトを共有する方法で効率的に支持する。
JavaのStringタイプ
JAVA言語では、Stringタイプは享元モードを使用しています。Stringオブジェクトはfinalタイプで、オブジェクトが作成されると変更できません。JAVAでは文字列の常量は常量池に存在しています。JAVAでは文字列の常量は常量池の中で一つしかコピーしないことを確認します。String a="abc"は、文字列の定数です。

public class Test {
 public static void main(String[] args) {
  String a = "abc";
  String b = "abc";
  System.out.println(a==b);
 }
}
上記の例では、trueとなります。これは、aとbの両方の参照が定数プールの同じ文字列定数「abc」を指していることを示しています。このような設計は、N個の同じオブジェクトを作成する際に発生する不必要な大量のリソース消費を回避する。
享元モードの構造
享元モードは同じコンテンツオブジェクトを大量に持つオーバーヘッドを回避するために共有を採用する。このようなオーバーヘッドは最も一般的で、最も直感的なのはメモリのロスです。享元対象が共有できる鍵は、内緒状態と外伝状態を区別することである。
       一つの内包状態は、享元オブジェクトの内部に格納され、環境の変化によって異なることはない。したがって、1つの享要素は、含蓄状態を有し、共有することができる。
    一つの含蓄の状態は環境の変化によって変えられます。共有できません。享元オブジェクトの含蓄状態はクライアントによって保存されなければならず、享元オブジェクトが作成された後、使用が必要な時にまた享元オブジェクトの内部に入る。含蓄の状態は享元オブジェクトの含蓄状態に影響してはいけません。それらは相互に独立しています。
享元モードは単純享元モードと複合享元モードの2つの形態に分けることができる。
単純享元モード
単純な享元モードでは、すべての享元オブジェクトを共有することができる。

単純享元モードに関わるキャラクターは以下の通りです。
抽象的な享元(Flyweight)の役割:抽象的なインターフェースを提供し、具体的な享元キャラクターのすべての実現方法を規定する。
具体的な享元(Cocrete Flyweight)役:抽象的な享元役が規定したインターフェースを実現する。含蓄状態があるなら、含蓄状態のための記憶空間を提供する責任があるべきです。
享元工場(Flyweight Factory)役:本役は享元役の作成と管理を担当しています。本キャラは享元オブジェクトがシステムにより適切に共有されることを保証しなければならない。クライアントオブジェクトが享元オブジェクトを呼び出した時、享元工場の役割はシステムに要求に合致した享元オブジェクトがあるかどうかを確認します。もしすでにあったら、享元工場の役割はこの既存の享元対象を提供しなければならない。もしシステムに適当な享元対象がないなら、享元工場の役割は適当な享元対象を作るべきです。
ソースコード
抽象的な享元キャラクタークラス

public interface Flyweight {
 //       ,  state     
 public void operation(String state);
}
具体的な享元キャラクタークラスのCocrete Flyweightには、本例ではCharcterタイプのintrinicState属性の代表があり、その値は享元オブジェクトが作成された時に付与されるべきである。すべての含蓄の状態はオブジェクトが作成された後、変更されません。
一つの享元オブジェクトが含蓄状態にある場合、すべての外部状態はクライアントに記憶されなければならず、享元オブジェクトを使用する時、クライアントから享元オブジェクトに伝えられる。ここには外伝的な状態が一つしかありません。operation()方法のパラメータstateは外部から伝えられた外伝的な状態です。

public class ConcreteFlyweight implements Flyweight {
 private Character intrinsicState = null;
 /**
 *     ,          
 * @param state
 */
 public ConcreteFlyweight(Character state){
  this.intrinsicState = state;
 }
 
 /**
 *              ,       ,
 *              。
 */
 @Override
 public void operation(String state) {
  // TODO Auto-generated method stub
  System.out.println("Intrinsic State = " + this.intrinsicState);
  System.out.println("Extrinsic State = " + state);
 }
}
享元工場の役割類は、クライアントが直接的に享元を実例化することができず、工場のオブジェクトを通して、一つのfactory()方法を利用して享元対象を得る必要があることを指摘しなければならない。一般的に、享元工場の対象はシステム全体で一つしかないので、単例モードを使ってもいいです。
クライアントが単純な享元オブジェクトを必要とする場合は、享元工場のfactory()方法を呼び出し、必要な単純享元オブジェクトの内緒状態を伝え、工場法により必要な享元オブジェクトを生成する。

public class FlyweightFactory {
 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
 
 public Flyweight factory(Character state){
  //         
  Flyweight fly = files.get(state);
  if(fly == null){
   //              Flyweight  
   fly = new ConcreteFlyweight(state);
   //     Flyweight        
   files.put(state, fly);
  }
  return fly;
 }
}
クライアントクラス

public class Client {

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  FlyweightFactory factory = new FlyweightFactory();
  Flyweight fly = factory.factory(new Character('a'));
  fly.operation("First Call");
  
  fly = factory.factory(new Character('b'));
  fly.operation("Second Call");
  
  fly = factory.factory(new Character('a'));
  fly.operation("Third Call");
 }
}

クライアントは3つの享元オブジェクトを申請しましたが、実際に作成した享元オブジェクトは2つしかなく、これは共有の意味です。運転結果は以下の通りです。

複合享元モード
単純享元モードでは、すべての享元対象は単純享元対象であり、つまり直接共有できる。もっと複雑な場合には、いくつかの単純享元を合成モードを用いて複合し、複合享元オブジェクトを形成する。このような複合享元オブジェクト自体は共有できないが、それらは単純享元オブジェクトに分解され、後者は共有できる。

複合享元役に関わるキャラクターは以下の通りです。
抽象的な享元(Flyweight)の役割:抽象的なインターフェースを提供し、具体的な享元キャラクターのすべての実現方法を規定する。
具体的な享元(Cocrete Flyweight)役:抽象的な享元役が規定したインターフェースを実現する。含蓄状態があるなら、含蓄状態のための記憶空間を提供する責任があるべきです。
複合享元(Cocrete ComputsiteFlyweight)キャラクター:複合享元キャラの代表的なオブジェクトは共有できないが、複合享元オブジェクトは複数の自身が単なる享元オブジェクトの組み合わせに分解することができる。複合享元キャラは共有できない享元対象とも言われています。
享元工場(Flyweight Factory)役:本役は享元役の作成と管理を担当しています。本キャラは享元オブジェクトがシステムにより適切に共有されることを保証しなければならない。クライアントオブジェクトが享元オブジェクトを呼び出した時、享元工場の役割はシステムに要求に合致した享元オブジェクトがあるかどうかを確認します。もしすでにあったら、享元工場の役割はこの既存の享元対象を提供しなければならない。もしシステムに適当な享元対象がないなら、享元工場の役割は適当な享元対象を作るべきです。
ソースコード
抽象的な享元キャラクタークラス

public interface Flyweight {
 //       ,  state     
 public void operation(String state);
}
具体的な享元キャラクタークラス

public class ConcreteFlyweight implements Flyweight {
 private Character intrinsicState = null;
 /**
 *     ,          
 * @param state
 */
 public ConcreteFlyweight(Character state){
  this.intrinsicState = state;
 }
 
 /**
 *              ,       ,
 *              。
 */
 @Override
 public void operation(String state) {
  // TODO Auto-generated method stub
  System.out.println("Intrinsic State = " + this.intrinsicState);
  System.out.println("Extrinsic State = " + state);
 }

}

複合享元オブジェクトは、単純享元オブジェクトが複合によって形成されるので、add()のような凝集管理方法を提供する。複合享元オブジェクトは異なる凝集要素を持つため、これらの凝集要素は複合享元オブジェクトが作成された後に加入される。これ自体は複合享元オブジェクトの状態が変化することを意味し、複合享元オブジェクトは共有できない。
複合享元役は抽象的な享元役によって規定されたインターフェース、すなわちoperation()方法を実現しており、この方法には一つのパラメータがあり、複合享元オブジェクトの含蓄状態を表している。一つの複合享元オブジェクトのすべての単純享元オブジェクトの含蓄状態は、複合享元オブジェクトの含蓄状態と同じである。一つの複合享元オブジェクトに含まれる単純享元オブジェクトの含蓄状態は一般的に同じではない。

public class ConcreteCompositeFlyweight implements Flyweight {
 
 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
 /**
 *                 
 */
 public void add(Character key , Flyweight fly){
  files.put(key,fly);
 }
 /**
 *               
 */
 @Override
 public void operation(String state) {
  Flyweight fly = null;
  for(Object o : files.keySet()){
   fly = files.get(o);
   fly.operation(state);
  } 
 }
}
享元工場の役割は、単純享元オブジェクトを提供するための二つの異なる方法を提供し、もう一つは複合享元オブジェクトを提供するためのものである。

public class FlyweightFactory {
 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
 /**
 *         
 */
 public Flyweight factory(List<Character> compositeState){
  ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
  
  for(Character state : compositeState){
   compositeFly.add(state,this.factory(state));
  }
  
  return compositeFly;
 }
 /**
 *         
 */
 public Flyweight factory(Character state){
  //         
  Flyweight fly = files.get(state);
  if(fly == null){
   //              Flyweight  
   fly = new ConcreteFlyweight(state);
   //     Flyweight        
   files.put(state, fly);
  }
  return fly;
 }
}
クライアントの役割

public class Client {

 public static void main(String[] args) {
  List<Character> compositeState = new ArrayList<Character>();
  compositeState.add('a');
  compositeState.add('b');
  compositeState.add('c');
  compositeState.add('a');
  compositeState.add('b');
  
  FlyweightFactory flyFactory = new FlyweightFactory();
  Flyweight compositeFly1 = flyFactory.factory(compositeState);
  Flyweight compositeFly2 = flyFactory.factory(compositeState);
  compositeFly1.operation("Composite Call");
  
  System.out.println("---------------------------------");  
  System.out.println("              :" + (compositeFly1 == compositeFly2));
  
  Character state = 'a';
  Flyweight fly1 = flyFactory.factory(state);
  Flyweight fly2 = flyFactory.factory(state);
  System.out.println("              :" + (fly1 == fly2));
 }
}

運転結果は以下の通りです。

実行結果から、複合享元オブジェクトのすべての単純享元オブジェクトの含蓄状態は、複合享元オブジェクトの含蓄状態と同じであることがわかった。つまり外注の状態はすべてCompsite Callに等しいです。
実行結果から,複合享元オブジェクトに含まれる単純享元オブジェクトの含蓄状態は一般的には等しくないことがわかった。すなわち、含蓄状態はそれぞれb、c、aである。
運転結果から、複合享元オブジェクトは共有できないことがわかった。つまり同じ対象のcompsiteStateを使って工場を通して2回ずつ作成した対象は同じ対象ではない。
運行結果から、単純な享元対象は共有できるということが分かります。同じ対象stateを使って工場を通して2回ずつ作成した対象は同じ対象です。
享元モデルの長所と短所
        享元モードの利点はメモリ中のオブジェクトの数を大幅に低減することである。しかし、それを達成するために払った代価も高いです。
享元モードはシステムをより複雑にする。オブジェクトを共有できるように、いくつかの状態を外部化する必要があります。これは、プログラムの論理を複雑にします。
享元モードは、享元オブジェクトの状態を外部化し、外部状態を読み取ることで、運転時間が少し長くなります。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。