
コンピュータプログラミングにおいて、フライウェイト・ソフトウェア設計パターンとは、他の類似オブジェクトとデータの一部を共有することでメモリ使用量を最小限に抑えるオブジェクトを指します。フライウェイト・パターンは、23あるGoF設計パターンの1つです。[ 1 ]
他の文脈では、データ構造を共有するという考え方をハッシュコンシングと呼びます。
この用語は、ポール・カルダーとマーク・リントンによって1990年に初めて造語され、そのアイデアは広く研究されました。 [ 2 ] WYSIWYG文書エディタでグリフ情報を効率的に処理するために使用されました。[ 3 ]しかし、同様の技術は1988年にはすでに他のシステムで使用されていました。[ 4 ]
概要
フライウェイトパターンは、多数のオブジェクトが単純な繰り返し要素を共有する場合に役立ちます。これらのオブジェクトを個別に埋め込むと、大量のメモリを消費してしまいます。共有データは外部データ構造に保持し、オブジェクトが使用される際に一時的に渡すのが一般的です。
典型的な例としては、ワードプロセッサで文字を表現するために使用されるデータ構造が挙げられます。単純に考えると、文書内の各文字は、フォントアウトライン、フォントメトリック、その他の書式設定データを含むグリフオブジェクトを持つことになります。しかし、これでは文字ごとに数百バイト、あるいは数千バイトものメモリを消費してしまいます。そこで、各文字は、文書内の同じ文字のすべてのインスタンスで共有されるグリフオブジェクトへの参照を持つことができます。こうすることで、内部的には各文字の位置のみを保持すれば済みます。
その結果、フライウェイトオブジェクトは次のことが可能になります。[ 5 ]
- 不変で、コンテキストに依存せず、共有可能な固有の状態(たとえば、特定の文字セット内の文字「A」のコード)を格納する
- 可変でコンテキスト依存であり共有できない外部状態を渡すためのインターフェースを提供する(たとえば、テキスト文書内の文字「A」の位置)
クライアントはFlyweightオブジェクトを再利用し、必要に応じて外部状態を渡すことができるため、物理的に作成されるオブジェクトの数を減らすことができます。
構造

Clientフライウェイトパターンを使用するクラスFlyweightFactoryオブジェクトを作成して共有するクラスFlyweight- インターフェース
Flyweightは外部状態を受け取り、操作を実行します - 固有の状態
Flyweight1を実装して保存するクラスFlyweight
シーケンス図は、次の実行時の相互作用を示しています。
- オブジェクトはを
Client呼び出し、オブジェクトを返します。getFlyweight(key)FlyweightFactoryFlyweight1 operation(extrinsicState)返されたオブジェクトを呼び出した後Flyweight1、 はClient再度 を 呼び出しgetFlyweight(key)ますFlyweightFactory。FlyweightFactory既存のオブジェクトを返しますFlyweight1。
実装の詳細
flyweightパターンを実装する方法は複数あります。一例として、可変性、つまりflyweightの外部状態を格納するオブジェクトが変更可能かどうかが挙げられます。
不変オブジェクトは簡単に共有できますが、状態が変化するたびに新しい外部オブジェクトを作成する必要があります。一方、可変オブジェクトは状態を共有できます。可変性により、古くて使用されていないオブジェクトのキャッシュと再初期化が可能になり、オブジェクトの再利用性が向上します。状態が大きく変化する場合は、通常、共有は不可能です。
その他の主な懸念事項としては、検索 (エンドクライアントがフライウェイトにアクセスする方法)、キャッシュ、同時実行性などがあります。
検索
flyweight オブジェクトを作成または再利用するためのファクトリーインターフェースは、多くの場合、複雑な基盤システムのファサードとして機能します。例えば、flyweight オブジェクトを作成するためのグローバルアクセスを提供するために、ファクトリーインターフェースはシングルトンとして実装されるのが一般的です。
一般的に言えば、取得アルゴリズムは、ファクトリ インターフェイスを介した新しいオブジェクトの要求から始まります。
通常、リクエストはオブジェクトの種類に応じて適切なキャッシュに転送されます。リクエストがキャッシュ内のオブジェクトによって満たされる場合、そのオブジェクトは再初期化されて返されます。そうでない場合は、新しいオブジェクトがインスタンス化されます。オブジェクトが複数の外部サブコンポーネントに分割されている場合は、オブジェクトが返される前に、それらのサブコンポーネントが結合されます。
キャッシング
flyweight オブジェクト をキャッシュする方法には、メンテナンスされるキャッシュとメンテナンスされないキャッシュの 2 つがあります。
状態が大きく変化するオブジェクトは、 FIFO構造でキャッシュできます。この構造では、未使用のオブジェクトはキャッシュ内に保持されるため、キャッシュを検索する必要がありません。
対照的に、メンテナンスされていないキャッシュでは、初期オーバーヘッドが少なくなります。キャッシュのオブジェクトは、コンパイル時または起動時に一括初期化されます。オブジェクトがキャッシュに読み込まれると、オブジェクト取得アルゴリズムは、メンテナンスされたキャッシュのプッシュ/ポップ操作よりも多くのオーバーヘッドを伴う可能性があります。
不変状態を持つ外部オブジェクトを取得する場合は、単にキャッシュ内で目的の状態のオブジェクトを検索する必要があります。そのようなオブジェクトが見つからない場合は、その状態を持つオブジェクトを初期化する必要があります。可変状態を持つ外部オブジェクトを取得する場合は、使用済みオブジェクトが見つからない場合は、キャッシュ内で未使用のオブジェクトを検索し、再初期化する必要があります。未使用のオブジェクトが見つからない場合は、新しいオブジェクトをインスタンス化してキャッシュに追加する必要があります。
外部オブジェクトの固有のサブクラスごとに個別のキャッシュを使用できます。複数のキャッシュを個別に最適化し、各キャッシュに固有の検索アルゴリズムを関連付けることができます。このオブジェクトキャッシュシステムは、Chain of Responsibilityパターンでカプセル化することができ、コンポーネント間の疎結合を促進します。
同時実行性
フライウェイトオブジェクトが複数のスレッドで作成される場合は、特別な配慮が必要です。値のリストが有限で事前にわかっている場合、フライウェイトは事前にインスタンス化しておき、複数のスレッドでコンテナから競合なく取得できます。フライウェイトが複数のスレッドでインスタンス化される場合、以下の2つの方法があります。
- flyweight のインスタンス化をシングルスレッドにして、競合を導入し、値ごとに 1 つのインスタンスを確保します。
- 同時スレッドが複数の flyweight インスタンスを作成できるようにすることで、競合を排除し、値ごとに複数のインスタンスを許可します。
クライアントとスレッド間の安全な共有を可能にするために、flyweight オブジェクトを不変値オブジェクトにすることができます。この場合、2 つのインスタンスの値が等しい場合は、それらのインスタンスは等しいとみなされます。
例
C#
この例では、MyObjectクラスのすべてのインスタンスはクラスを使用してPointerデータを提供します。
// 自身を繰り返す Flyweight オブジェクトを定義します。public class Flyweight { public string Name { get ; set ; } public string Location { get ; set ; } public string Website { get ; set ; } public byte [ ] Logo { get ; set ; } }パブリック静的クラスPointer {パブリック静的読み取り専用Flyweight Company = new Flyweight {名前= "ABC" 、場所= "XYZ" 、ウェブサイト= "www.example.com" }; }public class MyObject { public string Name { get ; set ; } public string Company => Pointer . Company . Name ; }C++
C++標準テンプレートライブラリは、キーに一意のオブジェクトをマッピングできるコンテナをいくつか提供しています。コンテナを使用することで、一時オブジェクトを作成する必要がなくなり、メモリ使用量をさらに削減できます。
stdをインポートします。テンプレート< typename K 、typename V >、TreeMap = std :: map < K 、V >を使用します。String = std :: stringを使用します。StringView = std :: string_viewを使用します。テンプレート< typename K 、typename V >、HashMap = std :: unordered_map < K 、V >を使用します。// Tenant のインスタンスは Flyweights になりますclass Tenant { private : const String name ; public : explicit Tenant ( StringView name ) : name { name } {}[[ nodiscard ]]文字列getName () const noexcept { return name ; } };// Registry は、Tenant flyweight オブジェクトのファクトリーおよびキャッシュとして機能します。class Registry { private : HashMap < String , Tenant > tenants ; public : Registry ( ) = default ;[[ nodiscard ] ] Tenant & findByName ( StringView name ) { if ( ! tenants.contains ( name )) { tenants [ name ] = Tenant { name }; } return tenants [ name ]; } } ;// アパートメントは、一意のテナントを部屋番号にマッピングします。class Apartment { private : TreeMap < int , Tenant *> occupants ; Registry registry ; public : Apartment () = default ;void addOccupant ( StringView name , int room ) { occupants [ room ] = & registry . findByName ( name ); }void printTenants () { // room: int、tenant: Tenant for ( const auto & [ room 、tenant ] : occupants ) { std :: println ( "{} は部屋 {} を占有しています" 、tenant . name (), room ); } } };int main ( int argc , char * argv [ ]) {アパートメント;アパート。addOccupant ( "David" , 1 );アパート。addOccupant ( "サラ" , 3 );アパート。addOccupant ( "ジョージ" , 2 );アパート。addOccupant ( "サラ" , 12 );アパート。addOccupant ( "マイケル" , 10 );アパート。printTenants ();0を返す; }PHP
<?phpクラスCoffeeFlavour {プライベート静的配列$CACHE = [];プライベート関数__construct (プライベート文字列$name ) {}パブリック静的関数intern (文字列$name ) : self { self :: $CACHE [ $name ] ??= new self ( $name ); return self :: $CACHE [ $name ]; }パブリック静的関数flavoursInCache () : int { return count ( self :: $CACHE ); }パブリック関数__toString () :文字列{戻り値$this -> name ; }}クラスOrder {プライベート関数__construct (プライベートCoffeeFlavour $flavour 、プライベートint $tableNumber ) {}パブリック静的関数create ( string $flavourName 、int $tableNumber ) : self { $flavour = CoffeeFlavour :: intern ( $flavourName ); return new self ( $flavour 、$tableNumber ); }public function __toString () : string { return " { $this -> flavour }をテーブル{ $this -> tableNumber }に提供しています" ; } }クラスCoffeeShop {プライベート配列$orders = [];パブリック関数takeOrder (文字列$flavour 、int $tableNumber ) { $this -> orders [] = Order :: create ( $flavour 、$tableNumber ); }パブリック関数サービス() { print ( implode ( PHP_EOL 、$this -> orders ) . PHP_EOL ); } }$shop = new CoffeeShop (); $shop -> takeOrder ( "カプチーノ" , 2 ); $shop -> takeOrder ( "フラッペ" , 1 ); $shop -> takeOrder ( "エスプレッソ" , 1 ); $shop -> takeOrder ( "フラッペ" , 897 ); $shop -> takeOrder ( "カプチーノ" , 97 ); $shop -> takeOrder ( "フラッペ" , 3 ); $shop -> takeOrder ( "エスプレッソ" , 3 ); $shop -> takeOrder ( "カプチーノ" , 3 ); $shop -> takeOrder ( "エスプレッソ" , 96 ); $shop -> takeOrder ( "フラッペ" , 552 ); $shop -> takeOrder ( "カプチーノ" , 121 ); $shop -> takeOrder ( "エスプレッソ" , 121 ); $shop -> service (); print ( "キャッシュ内のCoffeeFlavorオブジェクト: " . CoffeeFlavour :: flavoursInCache () . PHP_EOL );参照
参考文献
- ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994).デザインパターン:再利用可能なオブジェクト指向ソフトウェアの要素. Addison Wesley. pp. 195ff . ISBN 978-0-201-63361-0。
{{cite book}}: CS1 maint: 複数の名前: 著者リスト (リンク) - ^ガンマ、エリック、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシデス(1995).デザインパターン:再利用可能なオブジェクト指向ソフトウェアの要素.アディソン・ウェズレー. pp. 205–206 . ISBN 978-0-201-63361-0。
- ^ Calder, Paul R.; Linton, Mark A. (1990年10月). 「Glyphs: Flyweight Objects for User Interfaces」. Proceedings of the 3rd annual ACM SIGGRAPH symposium on User interface software and technology - UIST '90 . The 3rd Annual ACM SIGGRAPH symposium on User Interface Software and Technology. Snowbird, Utah, United States. pp. 92– 101. doi : 10.1145/97924.97935 . ISBN 0-89791-410-4。
- ^ウェイナンド、アンドレ;ガンマ、エーリッヒ。マーティ、ルドルフ(1988)。ET++ - C++ のオブジェクト指向アプリケーション フレームワーク。OOPSLA (オブジェクト指向プログラミング システム、言語、およびアプリケーション)。米国カリフォルニア州サンディエゴ。ページ 46 ~ 57。CiteSeerX 10.1.1.471.8796。土井:10.1145/62083.62089。ISBN 0-89791-284-5。
- ^ 「JavaでFlyweightパターンを実装する」 Developer.com 2019年1月28日2021年6月12日閲覧。
- ^ 「Flyweightデザインパターン - 構造とコラボレーション」w3sDesign.com . 2017年8月12日閲覧。