惰性初始化

在程式設計中，惰性初始化（lazy initialization），是一種拖延戰術。在第一次需求出現以前，先延遲創建物件、計算值或其它昂貴程序。這通常是以一個旗號來實現，用旗號來標示是否完成其程式。每次請求對象時，會先測試此旗號。如果已完成，直接傳回，否則當場執行。

對於此想法更一般的論述，可見惰性求值。對指令式語言，這個模式可能潛藏著危險，尤其是使用共享狀態的程式習慣。

## 惰性工廠

《設計模式》書中的工廠方法模式的早於C++98的實現，由於這裡的工廠方法`CreateProduct()`總是虛函數並且經常是純虛函數，`Creator`在其構造子中只將具體產品初始化是為`0`，轉而通過其訪問子來返回這個產品；訪問子`GetProduct()`在需要的時候創建這個產品，《設計模式》書中將這種技術稱為「惰性初始化」：

```cpp
class Creator {
public:
    Creator();
    Product* GetProduct();
protected:
    virtual Product* CreateProduct();
private:
    Product* _product;
};
Creator::Creator() {
    _product = 0;
}
Product* Creator::GetProduct() {
    if (_product == 0) {
        _product = CreateProduct();
    }
    return _product;
}
```

這種「惰性工廠」結合了二個構想：

- 工廠方法：使用工廠方法，來獲得類的實例。
- 惰性初始化技術：使用惰性初始化，實例化物件於其第一次被要求之時。

在軟體設計實踐中經常還會結合上第三個構想：

- 多例模式：將實例存儲在一個映射中，在以「相同」的參數要求一個實例之時，返回「同一個」單例，有人將這種參數化或為單例註冊類型名字的「惰性工廠」稱為惰性初始化模式。

## 理論電腦科學

在理論電腦科學領域中，惰性初始化（也叫做惰性陣列），是設計數據結構的技術，使其可以工作於不需要被初始化的記憶體。尤其是假定了要訪問`n`個（編號從 `1`到`n`）未初始化記憶體單元的一個表格`T`，並希望賦值這個陣列的`m`個單元，比如賦值`T[k<sub>i</sub>] := v<sub>i</sub>`，它針對鍵值對`(k<sub>1</sub>, v<sub>1</sub>), ..., (k<sub>m</sub>, v<sub>m</sub>)`，並具有所有的`k<sub>i</sub>`都是不同的。惰性初始化技術允許只用`O(m)`次運算來完成它，而非耗費`O(m+n)`次運算來首先初始化所有陣列單元。這項技術簡單的分配一個表格`V`以任意次序存儲鍵值對`(k<sub>i</sub>, v<sub>i</sub>)`，並為每個`i`在單元`T[k<sub>i</sub>]`中寫入鍵`k<sub>i</sub>`存儲在`V`中的位置，保留`T`的其他單元不初始化。這可以用來處理下列方式的查詢：在針對某個`k`查看單元`T[k]`的時候，可以在`{1, ..., m}`範圍內檢查`T[k]`；如果不在其中，則`T[k]`是未初始化的。否則檢查`V[T[k]]`，並驗證這個鍵值對的第一個組件等於`k`；如果不等於，則`T[k]`是未初始化的（它只是意外落入範圍`{1, ..., m}`中）；否則確知了`T[k]`是初始化的單元之一，而對應的值是這個鍵值對的第二個組件。

## 示例

### C++

C++的例子：

```cpp
import std;

template <typename K, typename V>
using HashMap = std::unordered_map<K, V>;
template <typename T>
using SharedPtr = std::shared_ptr<T>;
using String = std::string;

class Fruit {
private:
    static HashMap<String, SharedPtr<Fruit>> types = {};
    String type;

    // Note: constructor private forcing one to use static getFruit.
    explicit Fruit(const String& type): 
        type{type} {}
public:
    // Lazy Factory method, gets the Fruit instance associated with a certain type.
    // Creates new ones as needed.
    static SharedPtr<Fruit> getFruit(const String& type) {
        auto [it, inserted] = types.emplace(type, nullptr);
        if (inserted) {
            it->second = std::make_shared<Fruit>(type);
        }
        return it->second;
    }

    // For example purposes to see pattern in action.
    static void printCurrentTypes() {
        std::println("Number of instances made = {}", types.size());
        for (const auto& [type, fruit] : types) {
            std::println({}, type);
        }
        std::println();
    }
};

int main(int argc, char* argv[]) {
    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();
}
```

程序輸出為：

```output
Number of instances made = 1
Banana

Number of instances made = 2
Apple
Banana

Number of instances made = 2
Apple
Banana
```

### Java

Java例子：

```java
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);
    }
}
```

### C#

在下面的C#例子中，`Fruit` 類別本身在這裡不做任何事。`_typesDictionary` 變數則是一個存 `Fruit` 實例的 Dictionary 或 Map，其透過`typeName`來存取。

```csharp
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();
    }
}
```

### Python

Python的對象惰性實例化例子：

```python
from weakref import WeakSet
import functools

class invoke_from_class():
    def __init__(self, fn):
        self.fn = fn
        functools.update_wrapper(self, fn)
    def __get__(self, obj, objtype=None):
        self.from_class = True if obj is None else False
        return self
    def __call__(self, *args, **kwargs):
        if self.from_class is True:
            return self.fn(*args, **kwargs)

class Fruit():
    delay_set = WeakSet()
    def __new__(cls, name, **kwargs):
        obj = super().__new__(cls)
        obj.name = name
        obj.kwargs = kwargs
        cls.delay_set.add(obj)
        return obj
    @invoke_from_class
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            self.__dict__[key] = value
    def __call__(self):
        if self in type(self).delay_set:
            type(self).delay_set.remove(self)
            kwargs = self.kwargs
            del self.kwargs
            type(self).__init__(self, **kwargs)
    @classmethod
    def info(cls):
        print(f'Instances deferred: {len(cls.delay_set)}')
```

若要防止非預期的訪問到未初始化實例特性，可以在定義類中設置值為`None`的同名類特性，這裡的實現略過了可附加的多例模式。下面是其執行：

```pycon
>>> fruit1 = Fruit('Banana')
>>> fruit2 = Fruit('Apple')
>>> Fruit.info()
Instances deferred: 2
>>> fruit1()
>>> fruit2()
>>> Fruit.info()
Instances deferred: 0
```

Python的對象特性惰性初始化例子：

```python
import functools

class Employee():
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            self.__dict__[key] = value
    @functools.cached_property
    def report(self):
        #Spending a lot of time and effort to get a result
        result = "Concise report: small is beautiful."
        return result
```

其執行：

```pycon
>>> employee = Employee(name="Taciturn Man")
>>> employee.__dict__
{'name': 'Taciturn Man'}
>>> employee.report
'Concise report: small is beautiful.'
>>> employee.__dict__
{'name': 'Taciturn Man', 'report': 'Concise report: small is beautiful.'}
```

### Smalltalk

下面的Smalltalk例子，具有典型的訪問子方法，它返回使用惰性初始化的一個變量的值。

```smalltalk
height
    ^height ifNil: [height := 2.0].
```

非惰性的替代者，使用的初始化方法是在對象被創建時運行的，並且使用簡單的訪問子方法來取回這個值。

```smalltalk
initialize
    height := 2.0

height
    ^height
```

注意惰性初始化也可以用在非面向對象程式語言中。

### Ruby

下面的Ruby例子，具有惰性初始化的來自遠程服務的身份驗證令牌。`@auth_token`被緩存的方式也是記憶化的示例。

```ruby
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
```

## 外部連結

- Article " Java Tip 67: Lazy instantiation - Balancing performance and resource usage" by Philip Bishop and Nigel Warren
- Java code examples
- Use Lazy Initialization to Conserve Resources
- Description from the Portland Pattern Repository
- Lazy Initialization of Application Server Services
- Lazy Inheritance in JavaScript