コンピュータ プログラミングにおいて、遅延初期化とは、オブジェクトの作成、値の計算、またはその他のコストのかかるプロセスを、最初に必要になるまで遅らせる戦術です。これは、オブジェクトまたはその他のリソースのインスタンス化に特に関連する遅延評価の一種です。
これは通常、アクセサー メソッド (またはプロパティ ゲッター) を拡張して、キャッシュとして機能するプライベート メンバーがすでに初期化されているかどうかを確認することによって実現されます。初期化されている場合は、すぐに返されます。初期化されていない場合は、新しいインスタンスが作成され、メンバー変数に配置され、最初の使用時に呼び出し元に返されます。
オブジェクトにめったに使用されないプロパティがある場合、起動速度が向上します。平均的なプログラム パフォーマンスは、メモリ (条件変数用) と実行サイクル (条件変数をチェックするため) の点でわずかに低下する可能性がありますが、オブジェクトのインスタンス化の影響は、システムの起動フェーズに集中するのではなく、時間的に分散 (「償却」) されるため、平均応答時間が大幅に向上します。
マルチスレッドコードでは、競合状態を防ぐために、遅延初期化されたオブジェクト/状態へのアクセスを同期する必要があります。
「怠け者の工場」
ソフトウェア設計パターンの観点では、遅延初期化はファクトリ メソッド パターンと組み合わせて使用されることがよくあります。これは、次の 3 つのアイデアを組み合わせたものです。
- ファクトリメソッドを使用してクラスのインスタンスを作成する(ファクトリメソッドパターン)
- インスタンスをマップに保存し、同じパラメータを持つインスタンスの各リクエストに対して同じインスタンスを返す(マルチトンパターン)
- 遅延初期化を使用して、最初に要求されたときにオブジェクトをインスタンス化する (遅延初期化パターン)
例
アクションスクリプト3
以下は、 ActionScriptで遅延初期化を実装したクラスの例です。
パッケージexamples.lazyinstantiation { public class Fruit { private var _typeName : String ; private static var instanceByTypeName : Dictionary = new Dictionary (); public function Fruit ( typeName : String ) : void { this._typeName = typeName ; } public function get typeName () : String { return _typeName ; } public static function getFruitByTypeName ( typeName : String ) : Fruit { return instancesByTypeName [ typeName ] || = new Fruit ( typeName ) ; } public static function printCurrentTypes ( ) : void { for each ( var fruit : Fruit in instanceByTypeName ) { // 各値を反復処理しますtrace ( fruit.typeName ) ; } } } }
基本的な使い方:
パッケージ
{
import examples.lazyinstantiation ; public class Main { public function Main () : void { Fruit.getFruitByTypeName ( "Banana " ) ; Fruit.printCurrentTypes ( ) ; Fruit.getFruitByTypeName ( " Apple " ) ; Fruit.printCurrentTypes ( ) ; Fruit.getFruitByTypeName ( " Banana " ) ; Fruit.printCurrentTypes ( ) ; } } }
C
Cでは、遅延評価は通常、静的変数を使用して 1 つの関数または 1 つのソース ファイル内で実装されます。
関数内:
#include <string.h> #include <stdlib.h> #include <stddef.h> #include <stdio.h>
struct fruit { char * name ; struct fruit * next ; int number ; /* その他のメンバー */ };
struct fruit * get_fruit ( char * name ) { static struct fruit * fruit_list ; static int seq ; struct fruit * f ; for ( f = fruit_list ; f ; f = f -> next ) if ( 0 == strcmp ( name , f -> name )) return f ; if ( ! ( f = malloc ( sizeof ( struct fruit )))) return NULL ; if ( ! ( f -> name = strdup ( name ))) { free ( f ); return NULL ; } f -> number = ++ seq ; f -> next = fruit_list ; fruit_list = f ; return f ; }
/* サンプルコード */
int main ( int argc , char * argv []) { int i ; struct fruit * f ; if ( argc < 2 ) { fprintf ( stderr , "使用方法: 果物 fruit-name [...] \n " ); exit ( 1 ); } for ( i = 1 ; i < argc ; i ++ ) { if (( f = get_fruit ( argv [ i ]))) { printf ( "果物 %s: 番号 %d \n " , argv [ i ], f -> number ); } } return 0 ; }
代わりに 1 つのソース ファイルを使用すると、関係のない関数からは状態を隠したまま、複数の関数間で状態を共有できます。
フルーツ.h:
#ifndef _FRUIT_INCLUDED_
#define _FRUIT_INCLUDED_
struct fruit { char * name ; struct fruit * next ; int number ; /* その他のメンバー */ };
構造体fruit * get_fruit ( char * name ); void print_fruit_list ( FILE * file );
#endif /* _フルーツが含まれています_ */
フルーツ.c:
#include <string.h> #include <stdlib.h> #include <stddef.h> #include <stdio.h> #include "fruit.h"
静的構造体fruit * fruit_list ;静的int seq ;
struct fruit * get_fruit ( char * name ) { struct fruit * f ; for ( f = fruit_list ; f ; f = f -> next ) if ( 0 == strcmp ( name , f -> name )) return f ; if ( ! ( f = malloc ( sizeof ( struct fruit )))) return NULL ; if ( ! ( f -> name = strdup ( name ))) { free ( f ); return NULL ; } f -> number = ++ seq ; f -> next = fruit_list ; fruit_list = f ; return f ; }
void print_fruit_list ( FILE * file ) { struct fruit * f ; for ( f = fruit_list ; f ; f = f -> next ) fprintf ( file , "%4d %s \n " , f -> number , f -> name ); }
メイン.c:
#include <stdlib.h> #include <stdio.h> #include "fruit.h"
int main ( int argc , char * argv []) { int i ; struct fruit * f ; if ( argc < 2 ) { fprintf ( stderr , "使用方法: 果物 fruit-name [...] \n " ); exit ( 1 ); } for ( i = 1 ; i < argc ; i ++ ) { if (( f = get_fruit ( argv [ i ]))) { printf ( "果物 %s: 番号 %d \n " , argv [ i ], f -> number ); } } printf ( "次の果物が生成されました: \n " ); print_fruit_list ( stdout ); return 0 ; }
C#
.NET Framework 4.0では、Lazy遅延読み込みを行うために使用できるクラスがMicrosoftに含まれています。以下は、クラスの遅延読み込みを行うダミーコードです。Fruit
var lazyFruit = new Lazy < Fruit > (); Fruit fruit = lazyFruit . Value ;
以下はC#のダミーの例です。
クラスFruit自体はここでは何もしません。クラス変数は 、インスタンスを_typesDictionary格納するために使用される辞書/マップです。
FruittypeName
Systemを使用します。System.Collectionsを使用します。System.Collections.Genericを使用します。
パブリッククラスFruit {プライベート文字列_typeName ;プライベート静的IDictionary <文字列、Fruit > _typesDictionary = new Dictionary <文字列、Fruit > ();
プライベートFruit (文字列typeName ) { this . _typeName = typeName ; }
パブリック静的Fruit GetFruitByTypeName (文字列型) { Fruit fruit ;
if ( ! _typesDictionary . TryGetValue ( type , out fruit )) { // 遅延初期化fruit = new Fruit ( type );
_typesDictionary . Add ( type 、fruit ); }
フルーツを返す; }
public static void ShowAll () { if ( _typesDictionary.Count > 0 ) { Console.WriteLine ( "作成されたインスタンスの数 = {0} " 、_typesDictionary.Count ) ; foreach ( KeyValuePair < string 、Fruit > kvpin_typesDictionary ) { Console.WriteLine ( kvp.Key ) ; } Console.WriteLine ( ) ; } } public Fruit ( ) { //サンプルをコンパイルするために必要} }
クラスProgram { static void Main ( string [] args ) { Fruit . GetFruitByTypeName ( "Banana" ); Fruit . ShowAll ();
Fruit.GetFruitByTypeName ( " Apple" ) ; Fruit.ShowAll ( ) ;
// 「Banana」の Fruit が最初に作成されたときの既存のインスタンスを返しますFruit . GetFruitByTypeName ( "Banana" ); Fruit . ShowAll ();
コンソール.ReadLine ( );
} }
これは、型に 列挙型を使用している点を除けば、Lazy Initializationデザインパターンのかなり単純な「空欄を埋める」例です。
名前空間DesignPatterns.LazyInitialization ;
public class LazyFactoryObject { // 項目の内部コレクション// IDictionary はそれらが一意であることを確認しますprivate IDictionary < LazyObjectSize , LazyObject > _LazyObjectList = new Dictionary < LazyObjectSize , LazyObject > ();
// 必要なサイズの名前を渡すための列挙型
// 文字列を渡すことを回避し、LazyObject の一部になりますpublic enum LazyObjectSize { None 、Small 、Big 、Bigger 、Huge }
// 構築されるオブジェクトの標準型
public struct LazyObject { public LazyObjectSize Size ; public IList < int > Result ; }
// サイズを取得して「高価な」リストを作成します
private IList < int > Result ( LazyObjectSize size ) { IList < int > result = null ;
switch ( size ) { case LazyObjectSize.Small : result = CreateSomeExpensiveList ( 1 , 100 ) ; break ; case LazyObjectSize.Big : result = CreateSomeExpensiveList ( 1 , 1000 ) ; break ; case LazyObjectSize.Bigger : result = CreateSomeExpensiveList ( 1 , 10000 ) ; break ; case LazyObjectSize.Huge : result = CreateSomeExpensiveList ( 1 , 100000 ) ; break ; case LazyObjectSize.None : result = null ; break ; default : result = null ; break ; }
結果を返す; }
// 作成コストのかかる項目ではありませんが、要点は理解できます
// 必要になるまでコストのかかるオブジェクトの作成を遅らせますprivate IList < int > CreateSomeExpensiveList ( int start , int end ) { IList < int > result = new List < int > ();
for ( int counter = 0 ; counter < ( end - start ); counter ++ ) { result . Add ( start + counter ); }
結果を返す; }
public LazyFactoryObject () { // 空のコンストラクタ}
public LazyObject GetLazyFactoryObject ( LazyObjectSize size ) { // はい、これは文盲で不正確であることは承知していますLazyObject noGoodSomeOne ;
// out 経由でリストから LazyObjectSize を取得し、それ以外の場合は作成してリストに追加します。
if ( ! _LazyObjectList . TryGetValue ( size , out noGoodSomeOne )) { noGoodSomeOne = new LazyObject (); noGoodSomeOne . Size = size ; noGoodSomeOne . Result = this . Result ( size );
_LazyObjectList . Add ( size 、noGoodSomeOne ); }
noGoodSomeOneを返します。
C++
この例はC++です。
#include <iostream> #include <map> #include <string>
クラスFruit { public : static Fruit * GetFruit ( const std :: string & type ); static void PrintCurrentTypes ();
private :
// 注意: コンストラクターが private であるため、static |GetFruit| を使用する必要があります。Fruit ( const std :: string & type ) : type_ ( type ) {}
静的std :: map < std :: string , Fruit *>型;
std ::文字列型_ ; };
// 静的
std :: map < std :: string , Fruit *> Fruit :: types ;
// 遅延ファクトリメソッド。特定の
|type| に関連付けられた |Fruit| インスタンスを取得します。必要に応じて新しいインスタンスを作成します。
Fruit * Fruit::GetFruit ( const std :: string & type ) { auto [ it , inserted ] = types . emplace ( type , nullptr ); if ( inserted ) { it -> second = new Fruit ( type ); } return it -> second ; }
// パターンの動作を確認するための例です。
void Fruit::PrintCurrentTypes () { std :: cout << "Number of instance made = " << types . size () << std :: endl ; for ( const auto & [ type , fruit ] : types ) { std :: cout << type << std :: endl ; } std :: cout << std :: endl ; }
int main () {フルーツ:: GetFruit ( "バナナ" );フルーツ:: PrintCurrentTypes ();
フルーツ:: GetFruit ( "リンゴ" );
フルーツ:: PrintCurrentTypes ();
// 「Banana」の |Fruit| が初めて作成されたときの既存のインスタンスを返します
。Fruit :: GetFruit ( "Banana" ); Fruit :: PrintCurrentTypes (); }
// 出力:
//
// 作成されたインスタンスの数 = 1
// バナナ
//
// 作成されたインスタンスの数 = 2
// アップル
// バナナ
//
// 作成されたインスタンスの数 = 2
// アップル
// バナナ
//
結晶
クラスFruitプライベートゲッタータイプ: String @@types = {} of String => Fruit
def初期化( @type )終了
def self.get_fruit_by_type ( type : String ) @@ types [ type ] || = Fruit.new ( type )終了
def self . show_all puts "作成されたインスタンスの数: #{ @@types . size } " @@types . each do | type , fruit | puts " #{ type } " end puts end
def self . size @@types . size終了終了
フルーツ.get_fruit_by_type ( "バナナ" )フルーツ.show_all
フルーツ.get_fruit_by_type ( " Apple " )フルーツ.show_all
フルーツ.get_fruit_by_type ( "バナナ" )フルーツ.show_all
出力:
作成されたインスタンスの数: 1 バナナ 作成されたインスタンスの数: 2 バナナ りんご 作成されたインスタンスの数: 2 バナナ りんご
ハクセ
この例はHaxeにあります。[1]
クラスFruit { private static var _instances = new Map < String , Fruit >();
パブリック変数名(デフォルト、null ):文字列;
パブリック関数new ( name : String ) { this . name = name ; }
パブリック静的関数getFruitByName ( name : String ): Fruit { if ( ! _instances . exists ( name )) { _instances . set ( name , new Fruit ( name )); } return _instances . get ( name ); }
パブリック静的関数printAllTypes () { trace ([ for ( key in _instances . keys ()) key ]); } }
使用法
class Test { public static function main () { var banana = Fruit . getFruitByName ( "Banana" ); var apple = Fruit . getFruitByName ( "Apple" ); var banana2 = Fruit . getFruitByName ( "Banana" ); trace ( banana == banana2 ); // true. 同じバナナFruit . printAllTypes (); // ["Banana","Apple"] } }
ジャワ
この例はJavaです。
java.util.HashMapをインポートします。java.util.Mapをインポートします。java.util.Map.Entryをインポートします。
パブリッククラスプログラム{
/**
* @param args
*/
public static void main ( String [] args ) { Fruit . getFruitByTypeName ( FruitType .バナナ);フルーツ。すべて表示();フルーツ。getFruitByTypeName ( FruitType . apple );フルーツ。すべて表示();フルーツ。getFruitByTypeName ( FruitType .バナナ);フルーツ。すべて表示(); } }
enum FruitType {なし、リンゴ、バナナ、}
クラス フルーツ{
private static Map < FruitType , Fruit > types = new HashMap <> (); /** * プライベート コンストラクターを使用して、ファクトリー メソッドの使用を強制します。 * @param type */ private Fruit ( FruitType type ) { } /** * 遅延ファクトリー メソッド。特定の 型に関連付けられた Fruit インスタンスを取得します。必要に応じて新しいインスタンスを生成します。 * @param type 許可された任意のフルーツ型。例: APPLE * @return その型に関連付けられた Fruit インスタンス。 */ public static Fruit getFruitByTypeName ( FruitType type ) { Fruit fruit ; // これには同時実行性の問題があります。ここでは、types への読み取りが同期されていないため、 // types.putとtypes.containsKeyが同時に呼び出される可能性があります。// データが破損していても驚かないでください。if ( ! types.containsKey ( type )) { // 遅延初期化fruit = new Fruit ( type ); types.put ( type , fruit ); } else { // OK、現在は使用可能ですfruit = types . get ( type ); } return fruit ; } /** * 遅延ファクトリメソッド。特定の型に関連付けられた Fruit インスタンスを取得します。必要に応じて新しいインスタンスを作成します。 高度な並列環境で使用するために、 二重チェックのロックパターンを使用します。 * @param type 許可されたフルーツ型。例: APPLE * @return その型に関連付けられた Fruit インスタンス。 */ public static Fruit getFruitByTypeNameHighConcurrentVersion ( FruitType type ) { if ( ! types . containsKey ( type )) { synchronized ( types ) { // ロックを取得した後、再度チェックして、
//インスタンスはその間に別のスレッドによって作成されませんでしたif ( ! types.containsKey ( type ) ) { //遅延初期化types.put ( type , new Fruit ( type ) ) ; } } } return types.get ( type ); } /** *入力されたすべての フルーツを表示します。* / public static void showAll ( ) { if ( types.size ( ) > 0 ) { System.out.println ( "作成されたインスタンスの数= " + types.size ( ) ) ; for ( Entry < FruitType , Fruit > entry : types.entrySet ( ) ) { String fruit = entry.getKey ( ) . toString ( ) ; fruit = Character.toUpperCase ( fruit.charAt ( 0 ) ) + fruit.substring ( 1 ) ; System.out.println ( fruit ) ; } System.out.println ( ) ; } } }
出力
作成されたインスタンスの数 = 1 バナナ 作成されたインスタンスの数 = 2 バナナ りんご 作成されたインスタンスの数 = 2 バナナ りんご
JavaScript
var Fruit = ( function () { var types = {}; function Fruit () {};
// オブジェクト内の独自のプロパティを数える
function count ( obj ) { return Object . keys ( obj ). length ; }
var _static = { getFruit : function ( type ) { if ( typeof types [ type ] == 'undefined' ) { types [ type ] = new Fruit ; } return types [ type ]; }, printCurrentTypes : function () { console . log ( '作成されたインスタンスの数: ' + count ( types )); for ( var type in types ) { console . log ( type ); } } };
_staticを返します。
})();
フルーツ。getFruit ( 'Apple' );
フルーツ。printCurrentTypes ();
フルーツ。getFruit ( 'バナナ' );
フルーツ。printCurrentTypes ();
フルーツ。getFruit ( 'Apple' );
フルーツ。printCurrentTypes ();
出力
作成されたインスタンスの数: 1 りんご 作成されたインスタンスの数: 2 りんご バナナ 作成されたインスタンスの数: 2 りんご バナナ
PHP の
以下はPHP 7.4での遅延初期化の例です。
<?php
ヘッダー( 'Content-Type: text/plain; charset=utf-8' );
クラス Fruit
{
プライベート 文字列 $type ;
プライベート 静的 配列 $types = array ();
プライベート 関数 __construct (文字列 $type )
{
$this -> type = $type ;
}
public static function getFruit ( string $type )
{
// ここで遅延初期化が行われます
if ( ! isset ( self :: $types [ $type ])) {
self :: $types [ $type ] = new Fruit ( $type );
}
戻り 値:: $types [ $type ];
}
public static function printCurrentTypes () : void
{
echo '作成されたインスタンスの数:' . count ( self :: $types ) . " \n " ;
foreach ( array_keys ( self :: $types ) as $key ) {
echo " $key \n " ;
}
echo " \n " ;
}
}
フルーツ:: getFruit ( 'Apple' );
フルーツ:: printCurrentTypes ();
フルーツ:: getFruit ( 'バナナ' );
フルーツ:: printCurrentTypes ();
フルーツ:: getFruit ( 'Apple' );
フルーツ:: printCurrentTypes ();
/*
出力:
作成されたインスタンスの数: 1
Apple
作成されたインスタンスの数: 2
アップル
バナナ
作成されたインスタンスの数: 2
アップル
バナナ
*/
パイソン
この例はPythonです。
クラス Fruit :
def __init__ ( self , item : str )
: self.item = item
クラス FruitCollection :
def __init__ ( self ):
self . items = {}
def get_fruit ( self , item : str ) -> Fruit :
if item がself .itemsにない 場合: self .items [ item ] = Fruit ( item )
自分自身を返します。items [ item ]
__name__ == "__main__"の場合:
fruit = FruitCollection ()
print ( fruit . get_fruit ( "Apple" ))
print ( fruit . get_fruit ( "Lime" ))
ルビー
この例はRubyで、Google などのリモート サービスからの認証トークンを遅延初期化するものです。@auth_token がキャッシュされる方法も、メモ化の例です。
'net/http'クラスが必要ですBlogger def auth_token @auth_token ||= ( res = Net :: HTTP . post_form ( uri , params )) && get_token_from_http_response ( res ) end
# get_token_from_http_response、uri、paramsはクラスの後半で定義され
ます
b = Blogger.new b.instance_variable_get ( :@auth_token ) # nil を返すb.auth_token # tokenを返すb.instance_variable_get ( : @ auth_token ) # tokenを返す
スカラ
Scalaには遅延変数初期化のサポートが組み込まれています。[2]
scala > val x = { println ( "Hello" ); 99 }
Hello x : Int = 99 scala > lazy val y = { println ( "Hello!!" ); 31 } y : Int = < lazy > scala > y Hello !! res2 : Int = 31 scala > y res3 : Int = 31
雑談
この例はSmalltalkで、遅延初期化を使用して変数の値を返す 一般的なアクセサー メソッドです。
高さ
^高さ ifNil: [高さ := 2.0 ] 。
「非遅延」の代替案は、オブジェクトの作成時に実行される初期化メソッドを使用し、その後、より単純なアクセサー メソッドを使用して値を取得することです。
高さを 2.0に 初期化します
高さ
^高さ
遅延初期化は非オブジェクト指向言語でも使用できることに注意してください。
理論計算機科学
理論計算機科学の分野において、遅延初期化[3] (遅延配列とも呼ばれる) は、初期化する必要のないメモリで機能するデータ構造を設計する手法である。具体的には、 n個の初期化されていないメモリセル ( 1からnまで番号が付けられている)のテーブルTにアクセスでき、この配列のm個のセルを割り当てると仮定する。たとえば、すべての k i が異なるペア ( k 1 、 v 1 )、...、( k m 、 v m ) に対してT [ k i ] := v i を割り当てるとします。遅延初期化手法を使用すると、最初にすべての配列セルを初期化するために O ( m + n )操作を費やすのではなく、わずか O ( m ) 操作でこれを行うことができます。この手法は、ペア ( k i、v i ) を任意の順序で格納するテーブルVを割り当て、セルT [ k i ] 内の各iに対して、キーk iが格納されているV内の位置を書き込み、 Tの他のセルを初期化しないままにするだけです。これを使用して、次の方法でクエリを処理できます。あるkのセルT [ k ] を検索するときに、 kが範囲 {1, ..., m } 内にあるかどうかを確認できます。範囲内にない場合、T [ k ] は初期化されていません。それ以外の場合は、V [ T [ k ]] をチェックし、このペアの最初のコンポーネントがkに等しいことを確認します。そうでない場合、T [ k ] は初期化されていません (偶然に範囲 {1, ..., m } 内にあるだけです)。それ以外の場合は、T [ k ] が実際に初期化されたセルの 1 つであり、対応する値がペアの 2 番目のコンポーネントであることがわかります。
参照
参考文献
- ^ 「遅延初期化 - デザインパターン - Haxeプログラミング言語クックブック」 2018年1月11日. 2018年11月9日閲覧。
- ^ ポラック、デビッド (2009-05-25). Scala入門. Apress. ISBN 9781430219897。
- ^ Moret, BME; Shapiro, HD (1991). P から NP へのアルゴリズム、第 1 巻: 設計と効率。Benjamin/Cummings Publishing Company。pp. 191–192。ISBN 0-8053-8008-6。
外部リンク
- Philip Bishop と Nigel Warren による記事「Java ヒント 67: 遅延インスタンス化 - パフォーマンスとリソース使用量のバランス」
- Javaコード例
- 遅延初期化を使用してリソースを節約する
- ポートランドパターンリポジトリからの説明
- アプリケーションサーバーサービスの遅延初期化
- JavaScript における遅延継承
- C# における遅延継承
