Skip to content

Instantly share code, notes, and snippets.

@lalha
Last active January 3, 2016 05:09
Show Gist options
  • Save lalha/8413919 to your computer and use it in GitHub Desktop.
Save lalha/8413919 to your computer and use it in GitHub Desktop.
Extension Objectパターンの概要とDataSpiderでの利用例
public interface Adaptable {
Object getAdapter(Class adapter) throws Exception;
}
public abstract class AdapterFactory {
private final Class supportedObjectType;
private final Class[] supportedAdapterTypes;
protected AdapterFactory(Class supportedObjectType, Class[] supportedAdapterTypes) {
this.supportedObjectType = supportedObjectType;
this.supportedAdapterTypes = supportedAdapterTypes;
}
Class getSupportedObjectType() {
return supportedObjectType;
}
Class[] getSupportAdapterTypes() {
return supportedAdapterTypes;
}
public abstract Object getAdapter(Object o, Class adapterType) throws Exception;
}
public class AdapterManager {
private static final List FACTORIES = new ArrayList();
private AdapterManager() {
}
public static void register(AdapterFactory factory) {
FACTORIES.add(factory);
}
public static boolean hasAdapter(Object o, Class adapterType) {
return getAdapterFactory(o, adapterType) != null;
}
public static Object getAdapter(Object o, Class adapterType) throws Exception {
AdapterFactory factory = getAdapterFactory(o, adapterType);
if (factory == null)
return null;
return factory.getAdapter(o, adapterType);
}
private static AdapterFactory getAdapterFactory(Object o, Class adapterType) {
Class clazz = o.getClass();
while (clazz != null) {
AdapterFactory[] factories = getFactories(clazz);
for (int i = 0, length = factories.length; i < length; i++) {
AdapterFactory factory = factories[i];
Class[] types = factory.getSupportAdapterTypes();
for (int j = 0, j_length = types.length; j < j_length; j++) {
if (types[j] == adapterType) {
return factory;
}
}
}
clazz = clazz.getSuperclass();
}
return null;
}
private static AdapterFactory[] getFactories(Class objectType) {
List r = new ArrayList();
for (int i = 0, length = FACTORIES.size(); i < length; i++) {
AdapterFactory factory = (AdapterFactory) FACTORIES.get(i);
if (factory.getSupportedObjectType() == objectType) {
r.add(factory);
}
}
return (AdapterFactory[]) r.toArray(new AdapterFactory[r.size()]);
}
}

題材: プロパティーインスペクタの実装

ベタに実装すると・・・

(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クラスをプロパティーエディタに対応させる拡張」または「プロパティーエディタを追加する拡張」を行う際にオリジナルのモデルに手を加える必要がある。

そこでExtension Objectパターンの出番!

  • クライアント側に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;
	}
}

DataSpiderにおけるExtension Objectパターンの課題解決と活用

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のコンクリートクラスも数百となっている。

DataSpiderでのAdaptable#getAdapter()呼び出し箇所

クラスの拡張とオブジェクトの拡張

ここまで説明したのはExtension Objectパターンを用いたクラスの拡張例だが、クラスではなくオブジェクトを拡張することもできる。具体的には、例えばLayerSuperType(ObjectAdapterFactory対応版).javaのようにレイヤスーパータイプクラスを実装し、外部から拡張したいインスタンスに対して addObjectAdapterFactory(ObjectAdapterFactory) メソッドを使用してAdapterFactoryをインスタンスレベルで登録する。

public class LayerSuperType implements Adaptable {
private final List<ObjectAdapterFactory> objectAdapterFactoryList = new ArrayList<ObjectAdapterFactory>();
public Object getAdapter(Class adapterType) throws Exception {
Object adapter = null;
for (ObjectAdapterFactory factory : objectAdapterFactoryList) {
adapter = factory.getAdapter(adapterType);
if (adapter != null) {
return adapter;
}
}
return AdapterManager.getAdapter(this, adapterType);
}
public void addObjectAdapterFactory(ObjectAdapterFactory factory) {
objectAdapterFactoryList.add(factory);
}
public void removeObjectAdapterFactory(ObjectAdapterFactory factory) {
objectAdapterFactoryList.remove(factory);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment