跳至內容

惰性初始模式

維基百科,自由的百科全書

程式設計中, 惰性初始是一種拖延戰術。在第一次需求出現以前,先延遲創建物件、計算值或其它昂貴程序。

這通常是以一個旗號來實現,用旗號來標示是否完成其程式。每次請求對象時,會先測試此旗號。如果已完成,直接傳回,否則當場執行。

對於此想法更一般的論述,可見惰性求值

對指令式語言,這個模式可能潛藏著危險,尤其是使用共享狀態的程式習慣。

"惰性工廠"

設計模式的觀點來說,惰性初始通常會和工廠方法模式合作,這結合了三種構想:

  • 使用一個工廠去得到一個類別的實例(工廠方法模式)。
  • 將實例存在一個集合中,所以下次要求一個實例卻有相同參數時,可以得到同一個實例(可和單例模式來做比較)。
  • 在第一次時,使用惰性初始來實例化物件(惰性初始模式)。

示例

Fruit 類別本身在這裡不做任合事。_typesDictionary 變數則是一個存 Fruit 實例的 Dictionary 或 Map ,其透過typeName來存取。

using System;
using System.Collections;
using System.Collections.Generic;

public class Fruit
{
    private string _typeName;

    private static Dictionary<string, Fruit> _typesDictionary = new Dictionary<string, Fruit>();

    private Fruit(String typeName)
    {
        this._typeName = typeName;
    }

    public static Fruit GetFruitByTypeName(string type)
    {
        Fruit fruit;

        if (!_typesDictionary.ContainsKey(type))
        {
            // 惰性初始
            fruit = new Fruit(type);

            _typesDictionary.Add(type, fruit);
        }
        else
            fruit = _typesDictionary[type];

        return fruit;
    }

    public static void ShowAll()
    {
        if (_typesDictionary.Count > 0)
        {
            Console.WriteLine("Number of instances made = {0}", _typesDictionary.Count);
            
            foreach (KeyValuePair<string, Fruit> kvp in _typesDictionary)
            {
                Console.WriteLine(kvp.Key);
            }
            
            Console.WriteLine();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Fruit.GetFruitByTypeName("Banana");
        Fruit.ShowAll();

        Fruit.GetFruitByTypeName("Apple");
        Fruit.ShowAll();

        // returns pre-existing instance from first 
        // time Fruit with "Banana" was created
        Fruit.GetFruitByTypeName("Banana");
        Fruit.ShowAll();

        Console.ReadLine();
    }
}
import java.util.*;
  
public class Fruit
{
    private static final Map<String,Fruit> types = new HashMap<String,Fruit>();
    private final String type;
  
    // using a private constructor to force use of the factory method.
    private Fruit(String type) {
      this.type = type;
    }
    
    /**
     * Lazy Factory method, gets the Fruit instance associated with a
     * certain type. Instantiates new ones as needed.
     * @param type Any string that describes a fruit type, e.g. "apple"
     * @return The Fruit instance associated with that type.
     */
    public static synchronized Fruit getFruit(String type) {
      if(!types.containsKey(type))
        types.put(type, new Fruit(type)); // Lazy initialization
      return types.get(type);
    }
}
#include <iostream>
#include <string>
#include <map>
 
using namespace std;
 
class Fruit {
    public:
        static Fruit* getFruit(const string& type);
        static void printCurrentTypes();

    private:
        static map<string,Fruit*> types;
        string type;

        // note: constructor private forcing one to use static getFruit()
        Fruit(const string& t) : type( t ) {}
};

//definition needed for using any static member variable
map<string,Fruit*> Fruit::types; 
 
/*
 * Lazy Factory method, gets the Fruit instance associated with a
 * certain type. Instantiates new ones as needed.
 * precondition: type. Any string that describes a fruit type, e.g. "apple"
 * postcondition: The Fruit instance associated with that type.
 */
Fruit* Fruit::getFruit(const string& type) {
    Fruit *& f = types[type];   // try to find a pre-existing instance, or std::map'll create one if not found
 
    if (!f) {                   // if it was created by map automatically, it'll be pointing to NULL
        // couldn't find one, so make a new instance
        f = new Fruit(type); // lazy initialization part
    }
    return f;
}

/*
 * For example purposes to see pattern in action
 */
void Fruit::printCurrentTypes() {
    if (!types.empty()) {
        cout << "Number of instances made = " << types.size() << endl;
        for (map<string,Fruit*>::iterator iter = types.begin(); iter != types.end(); ++iter) {
            cout << (*iter).first << endl;
        }
        cout << endl;
    }
}

int main(void) {
    Fruit::getFruit("Banana");
    Fruit::printCurrentTypes();
 
    Fruit::getFruit("Apple");
    Fruit::printCurrentTypes();
 
    // returns pre-existing instance from first 
    // time Fruit with "Banana" was created
    Fruit::getFruit("Banana");
    Fruit::printCurrentTypes();
 
    return 0;
}

/*
輸出:
Number of instances made = 1
Banana
 
Number of instances made = 2
Apple
Banana
 
Number of instances made = 2
Apple
Banana
*/

The following is an example (in Smalltalk) of a typical accessor method to return the value of a variable using lazy initialization.

    height
        height ifNil: [height := 2.0].
        ^height
</source>
The 'non-lazy' alternative is to use an initialization method that is run when the object is created and then use a simpler accessor method to fetch the value.

<source lang="smalltalk">
    initialize
        height := 2.0

    height
        ^height

Note that lazy initialization can also be used in non-object-oriented languages.

The following is an example (in Ruby) of lazily initializing an authentication token from a remote service like Google. The way that @auth_token is cached is also an example of memoization.

require 'net/http'
class 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 and params are defined later in the class
end

b = Blogger.new
b.instance_variable_get(:@auth_token) # returns nil
b.auth_token # returns token
b.instance_variable_get(:@auth_token) # returns token
class Fruit:
    def __init__(self, type):
        self.type = type
    
class Fruits:
    def __init__(self):
        self.types = {}
    
    def get_fruit(self, type):
        if type not in self.types:
            self.types[type] = Fruit(type)
        
        return self.types[type]

if __name__ == '__main__':
    fruits = Fruits()
    print fruits.get_fruit('Apple')
    print fruits.get_fruit('Lime')
<?php
header('Content-type:text/plain; charset=utf-8');

class Fruit
{
    private $type;
    private static $types = array();

    private function __construct($type)
    {
        $this->type = $type;
    }

    public static function getFruit($type)
    {
        // Lazy initialization takes place here
        if(!array_key_exists($type, self::$types)) {
            self::$types[$type] = new Fruit($type);
        }

        return self::$types[$type];
    }

    public static function printCurrentTypes()
    {
        echo 'Number of instances made: ' . count(self::$types) . "\n";

        foreach(array_keys(self::$types) as $key) echo "$key\n";

        echo "\n";
    }
}

Fruit::getFruit('Apple');
Fruit::printCurrentTypes();

Fruit::getFruit('Banana');
Fruit::printCurrentTypes();

Fruit::getFruit('Apple');
Fruit::printCurrentTypes();

/*
OUTPUT:

Number of instances made: 1
Apple

Number of instances made: 2
Apple
Banana

Number of instances made: 2
Apple
Banana
*/
?>

另見

外部連結