(1) プロパティーインスペクタ側でswitch文
public void selectedItemUpdated(Object item) {
clearRow();
if (item instanceof File) {
File file = (File) item;
addRow("ファイル名", file.getFileName());
addRow("最終更新日", file.getLastUpdated());
...
} else if (item instanceof Directory) {
...
} else if (item instanceof Icon) {
...
} else if (...) {
...
}
}
→ 数が限られていて今後も追加の可能性がなければ良いが、そうでなければあまりにも直書きすぎてオブジェクト指向設計からかけ離れている
(2) モデルがPropertySourceインターフェースを実装
プロパティーインスペクタ側はこんなコードに。
public void selectedItemUpdated(Object item) {
if (item instanceof PropertySource) {
updateData((PropertySource) item);
}
}
そしてモデル側はこんなコードに(PropertySourceFactoryインターフェース実装の場合)。
public class File implements PropertySource {
// PropertySourceインターフェースの実装
public PropertyTableModel getTableModel() {
...
}
}
→ モデルにUIのためのコードが混在してしまいコード分量が膨れ上がることに加え、「Fileクラスをプロパティーエディタに対応させる拡張」または「プロパティーエディタを追加する拡張」を行う際にオリジナルのモデルに手を加える必要がある。
- クライアント側にswitch文を書きたくない
- 将来の拡張性を担保したい
- 拡張の際に既存クラスに手を入れることは避けたい(クラスを外部から拡張したい)
- モデルはシンプルな状態に保ちたい
プロパティーインスペクタのコードはこんな感じに
public void selectedItemUpdated(Object item) {
if (item instanceof Adaptable) {
PropertySource propertySource = (PropertySource) ((Adaptable) item).getAdapter(PropertySource.class);
if (propertySource != null) {
updateContents();
}
}
}
モデルは将来の拡張を見越して予めAdaptableインターフェースを実装しておく。
public class File implements Adaptable {
...
public Object getAdapter(Class adapterType) throws Exception {
return AdapterManager.getAdapter(this, adapterType);
}
}
そしてFileクラスをプロパティーインスペクタに対応させることになった場合、次のようなFileAdapterFactoryクラスを作成し、以下のようにAdapterManagerに登録することで「Fileクラスのプロパティーインスペクタ対応を外部から拡張する」ことを実現できる。
AdapterManager.register(new FileAdapterFactory());
class FileAdapterFactory extends AdapterFactory {
FileAdapterFactory() {
super(File.class, new Class[] { PropertySource.class }
}
public Object getAdapter(Object o, Class adapterType) throws Exception {
File file = (File) o;
if (adapterType == PropertySource.class) {
return new FilePropertySource(file);
}
return null;
}
}
Extension Objectパターンの概要は上述の通りだが、用途によってはクライアント側のAdapter#getAdapter()メソッドが頻繁に呼び出されたり、getAdapter()メソッドの中で生成されるオブジェクトが生成コストが大きかったりメモリを消費するようなオブジェクトだった場合、getAdapter()を呼び出すかどうかの判定メソッドを追加することが望ましい。
この観点から、DataSpiderではAdaptable#hasAdapter(Class adapter)メソッドを追加している。これにより、アダプタの有無を確認するためだけにアダプタのインスタスを生成する必要がなくなり、例えば「アダプタの有無でメニューのEnable/Disableが変わる」ような処理を実装する際の実行パフォーマンスとメモリ使用量を効率化することができる。
public interface Adaptable {
boolean hasAdapter(Class adapter);
Object getAdapter(Class adapter) throws Exception;
}
これに伴うクライアント側のコード変更は以下のようになる。 (実際にはDataSpiderではモデルのレイヤスーパータイプでAdaptableインターフェースを実装しているためより簡潔なコードとなっている)
public void selectedItemUpdated(Object item) {
if (item instanceof Adaptable) {
Adaptable adaptable = (Adaptable) item;
if (adaptable.hasAdapter(PropertySource.class)) {
updateContents((PropertySource) adaptable.getAdapter(PropertySource.class));
}
}
}
というわけで使いこなすと大変便利なExtension ObjectパターンのDataSpiderにおける活用箇所だが、基本的にUIのコンポーネントのうち、拡張性が求められる箇所はすべてこの形で実装されているため、下記の参照検索結果のように例に挙げたプロパティーインスペクタを含め、かなり多くの箇所で使われており、AdapterFactoryのコンクリートクラスも数百となっている。
ここまで説明したのはExtension Objectパターンを用いたクラスの拡張例だが、クラスではなくオブジェクトを拡張することもできる。具体的には、例えばLayerSuperType(ObjectAdapterFactory対応版).javaのようにレイヤスーパータイプクラスを実装し、外部から拡張したいインスタンスに対して addObjectAdapterFactory(ObjectAdapterFactory) メソッドを使用してAdapterFactoryをインスタンスレベルで登録する。