PoEAA ch11 Lazy Load
- Lazy Load
- How It Works
- When to Use It
- Example: Lazy Initialization
- Example: Virutal Proxy
- Example: Using a Value Holder (Java)
- Example: Using Ghosts (C#)
- 英語
Lazy Load
An object that doesn't contain all of the data you need but knows how to get it.
- DBからメモリ上のオブジェクトにデータをロードする時、関連するオブジェクトも一緒にロードするように設計すると便利
- クライアントコードで明示的にロードしなくて済む
- 【補】サービス層とか
- クライアントコードで明示的にロードしなくて済む
- 芋づる式にオブジェクトグラフ全部ロードするのはパフォーマンスに悪影響
- そこで読み込みを遅延させる
- 読み込む必要がなかったとき得する
How It Works
種類
- 主要な実装は4つある
- Lazy Initialization
- Virtual Proxy
- Value Holder
- Ghost
Lazy Initialization
- Kent Beckのパターン本に書いてあるらしい?
- get時、フィールドがnullかどうかチェックする
- nullなら算出してセットしてから返す
- nullが有意な値の場合は、Special Caseを適用せよ
- 【補】UnknownのnullとNot Applicableのnullがある
- 後者には例えばNull Object Pattern等を適用せよ、ってこと
- 【補】UnknownのnullとNot Applicableのnullがある
- DBのスキーマに依存しやすい
Virtual Proxy
- Data Mapper採用時はLazy Initializationがうまくいかないので、この間接層を使う
- ロード対象のオブジェクトをVirtual Proxyでくるむ
- get時はVirtual Proxyを返す
- Virtual Proxyのプロパティの初回アクセス時に読み込みを行う
- 利点
- ロード対象のオブジェクトそのものを返しているように見える
- 読み込み処理がVirtual Proxyの中でカプセル化されている
- Domain ModelはLazy Loadの存在を知らない
- ロード対象のオブジェクトそのものを返しているように見える
- 欠点
- 組み込みの等価判定でコケる
- 静的型付け言語では、Lazy Load対象のオブジェクトのクラスの数だけVirtual Proxyを作らなければならない
- コレクションに対してはこれらの欠点はあてはまらない
- 組み込みの等価判定でコケる
- コレクションは元々そう
- クラスの数だけVirtual Proxyを作らなければならない
- コレクションに対して1つ作れば良い
- 組み込みの等価判定でコケる
Value Holder
- Virtual Proxyのラップ対象をObjectに一般化した感じのもの
Ghost
- どういうやつ
- 一度に全フィールドをLazy LoadするLazy Initialization
- 自分自身のVirtual Proxy
- ロードステートを持たせる
GHOST
なら、プロパティアクセス時にロードを行うLOADED
なら、ロード済のフィールドを返す
- ripple loading
- AOPを適用してgetterにLazy Load処理を差し挟むとよい
- Domain Objectから切り離して透過的にできる
- ユースケースにより異なるlazinessが必要なケースではどうする?
- Data Mapper採用時は、lazinessごとにData Mapperを分ける
- Eager LoadするMapper
- Lazy LoadするMapper
- アプリケーションコード(サービス層とか)側で注入し分ける
- 何種類も作る必要は普通ない
- Data Mapper採用時は、lazinessごとにData Mapperを分ける
When to Use It
- DBから取得するデータの量と、アクセス回数による
- 読み込み量を減らすよりは回数を減らせ
- Serialized LOB等、データが大きい場合でさえも、余計なカラムを読み込むコストは大したこと無い
- 読み込み量を減らすよりは回数を減らせ
- 複雑性を増すため、必要のない場合は使わないのが良い
Example: Lazy Initialization
class Supplier... public List getProducts() { if (products == null) products = Product.findForSupplier(getId()); return products; }
Example: Virutal Proxy
- 本物のクラスに見えるプロキシで包むのがキモ
- pp.204-205のコードを一部改変・コメント付した
class Supplier... /** ProductsのリストのVirtual Proxy */ private VirtualList products;
/** * コレクション読み込みインタフェース * 具体的に何のクラスのオブジェクトをどのMapperで読み込むかは実装クラスで決める */ public interface VirutalListLoader { List load(); } /** * Productのコレクションを読み込むクラス * ProductMapperに読み込みを委譲 */ public static class ProductLoader implements VirtualListLoader { private Long id; public ProductLoader(Long id) { this.id = id; } public list Load() { return ProductMapper.create().findForSupplier(id); } }
/** * SupplierのMapper * Supplier構築時にProductのコレクションのLazy Loadも仕込む */ class SupplierMapper... protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException { String nameArg = rs.getString(2); Supplier result = new Supplier(id, nameArg); result.setProduct(new VirtualList(new ProductLoader(id))); return result; }
/** * ListのVirtual Proxy */ class VirtualList... { /** コレクション実体 */ private List source; /** データ読み込みクラスをコンストラクタ注入 */ private VirtualListLoader loader; public VirtualList(VirtualListLoader loader) { this.loader = loader; } /** * Lazy Load実部 */ private List getSource() { if (source == null) source = loader.load(); return source; } /** * Listが持っているメソッドぜんぶ委譲 */ public int size() { return getSource().size(); } public boolean isEmpty() { return getSOurce().isEmpty(); } ... }
- Domain Modelオブジェクトは、単にVirtualListを返せばいい
- Lazy Loadのことを知らない
Example: Using a Value Holder (Java)
class Supplier... private ValueHolder products; public List getProducts() { // ValueHolder.getValue()はObjectを返すので // ここでダウンキャストして返す return (List) products.getValue(); }
/** * ObjectのVirtual Proxy */ class ValueHolder... { /** オブジェクト実体 */ private Object value; /** データ読み込みクラスをコンストラクタ注入 */ private ValueLoader loader; public VirtualList(ValueLoader loader) { this.loader = loader; } /** * Lazy Load実部 */ private List getValue() { if (value == null) value = loader.load(); return value; }
- Domain ModelはLazy Loadの存在を意識する必要がある
- getterの中でValueHolderから値を取り出す必要がある
- 汎用のObjectが返るためダウンキャストが必要
- getterの中でValueHolderから値を取り出す必要がある
Example: Using Ghosts (C#)
- Domain ModelのLayer Supertypeに実装すると良い
class Employee public String Name { get { Load(); return _name; } set { Load(); _name = value; } } String _name; class DomainObject... protected void Load() { if (IsGhost) DataSource.Load(this); }
- 各getter/setterに
Load()
を書くのがかったるい- AOPを適用すると良い
DataSource.Load(DomainObject obj)
で適当なMapperに処理を委譲し、データの読み込み・フィールドのセットを行う- ドメイン層をデータソース層に依存させたくないので、Separated Interfaceを適用する
Mapper.Find(Long id)
では、Ghost Stateのオブジェクトを返すMapper.Load(Object obj)
の中身は?
英語
- in one fell swoop
- 一気に