惰性初始化

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

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

惰性工廠
《設計模式》書中的工厂方法模式的早于C++98的实现,由于这里的工厂方法CreateProduct()总是虚函数并且经常是纯虚函数,Creator在其构造子中只将具体产品初始化是为0,转而通过其访问子来返回这个产品;访问子GetProduct()在需要的时候创建这个产品,《设计模式》书中将这种技术称为“惰性初始化”:

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[ki] := vi,它针对键值对(k1, v1), ..., (km, vm),并具有所有的ki都是不同的。惰性初始化技术允许只用O(m)次运算来完成它,而非耗费O(m+n)次运算来首先初始化所有数组单元。这项技术简单的分配一个表格V以任意次序存储键值对(ki, vi),并为每个i在单元T[ki]中写入键ki存储在V中的位置,保留T的其他单元不初始化。这可以用来处理下列方式的查询:在针对某个k查看单元T[k]的时候,可以在{1, ..., m}范围内检查T[k];如果不在其中,则T[k]是未初始化的。否则检查V[T[k]],并验证这个键值对的第一个组件等于k;如果不等于,则T[k]是未初始化的(它只是意外落入范围{1, ..., m}中);否则确知了T[k]是初始化的单元之一,而对应的值是这个键值对的第二个组件。

示例
C++
C++的例子:

import std;

template
using HashMap = std::unordered_map;
template
using SharedPtr = std::shared_ptr;
using String = std::string;

class Fruit {
private:
static HashMap> 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 getFruit(const String& type) {
auto [it, inserted] = types.emplace(type, nullptr);
if (inserted) {
it->second = std::make_shared(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();
}

程序输出为:

Number of instances made = 1
Banana

Number of instances made = 2
Apple
Banana

Number of instances made = 2
Apple
Banana

Java
Java例子:

import java.util.*;

public class Fruit {
private static final Map types = new HashMap();
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來存取。

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

public class Fruit {
private string _typeName;
private static Dictionary _typesDictionary = new Dictionary();

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 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的对象惰性实例化例子:

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的同名类特性,这里的实现略过了可附加的。下面是其执行:

>> fruit1 = Fruit('Banana') >> fruit2 = Fruit('Apple') >> Fruit.info()

Instances deferred: 2
>>> fruit1()
>>> fruit2()
>>> Fruit.info()
Instances deferred: 0

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

其执行:

>> 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例子,具有典型的访问子方法,它返回使用惰性初始化的一个变量的值。

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

非惰性的替代者,使用的初始化方法是在对象被创建时运行的,并且使用简单的访问子方法来取回这个值。

initialize
height := 2.0

height
^height

注意惰性初始化也可以用在非面向对象编程语言中。

Ruby
下面的Ruby例子,具有惰性初始化的来自远程服务的身份验证令牌。 @auth_token被缓存的方式也是记忆化的示例。

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

另見

  • 單例模式
  • 享元模式
  • 代理模式
  • 惰性載入
  • 双重检查锁定模式

引用
外部連結

评论 (0)

  • 还没有评论,来抢沙发吧。