原文:http://www.eclipsezone.com/articles/what-is-iadaptable/
这里是翻译:http://bjzhanghao.cnblogs.com/archive/2005/09/24/243312.html
IAdaptable
is a key interface in the Eclipse arsenal. For old hands, its use trips off the fingers like exception handling or abstract classes. For the uninitiated, it may be quite scary. This article looks at what the IAdaptable
interface is, and how it can be used to extend existing functionality within Eclipse.
Casting to and from types
Since Java is a strongly typed language, each instance has a type associated with it. There are actually two sorts of type; the declared type and the runtime type (also known as the static type and dynamic type respectively). Weakly typed languages like Python are often called "untyped", but that's not strictly true; every instance has a runtime type -- you just don't need to declare it before use.
Back to Java; in order to invoke a method on an object, it needs to be visible at the declared type. In other words, you can only invoke methods that are defined in the parent type, even if the instance is of a known subtype:
List list = new ArrayList(); list.add("data"); // this is OK, list is valid list.ensureCapacity(4); // this is not, ensureCapacity() is ArrayList only,// declared type决定了对象的可见方法
If we need to invoke methods on the actual type, we first need to cast it to the appropriate type. In this case, we can cast ArrayList
to List
, because the ArrayList
type implements the List
interface. You can even test for this dynamically at runtime using:
list instanceof ArrayList
.
如果想调用真正类型,而非定义类型上的方法,必须要进行类型转换到真正的类型
Extensible interfaces
Unfortunately, a class doesn't always implement the interface you need. It might not implement it because there are only some cases where it is valid, or because it is a type in an unrelated library, or because the interface was developed after the original class was written.
This is where IAdaptable
comes in. You can think of IAdaptable
as a way of performing casts dynamically, rather than statically. So instead of using direct casting:(动态的类型转换)
Object o = new ArrayList(); List list = (List)o;
we could do something like:
IAdaptable adaptable = new ArrayList(); List list = (List)adaptable.getAdapter(java.util.List.class);
You can read this as a dynamic cast; what we're trying to do is convert adaptable
into a List
instance.
So why bother with the extra step of getAdapter()
when you can just use the cast directly? Well, this mechanism allows us to cast even when the target class doesn't implement the interface. For example, we might want to be able to use a HashMap
as a List
, despite the two otherwise incompatible classes. We could have:
IAdaptable adaptable = new HashMap(); List list = (List)adaptable.getAdapter(java.util.List.class);
Implementing IAdaptable
Most implementations of IAdaptable
look like a nested if statement for supported classes. If we were to implement getAdapter()
for our HashMap
class, it might look like:
public class HashMap implements IAdaptable { public Object getAdapter(Class clazz) { if (clazz == java.util.List.class) { List list = new ArrayList(this.size()); list.addAll(this.values()); return list; } return null; } // ... }
// 在职责分配上,由被转换的对象来确定如何进行转换,也只有该对象知道如何转换,被转换对象使用了本身的资源来完成转换对象的创建
What we're doing is returning an adapter to ourselves (or more specifically in this case, a copy), rather than doing the cast directly to the type. If the request is for a non-supported class, the convention is to return null
to indiciate failure, rather than throwing an exception. (Thus, when using adapters, it's not generally safe to assume they will return non-null value.)
PlatformObject
Of course, it would be a pain to have to edit the class when you want to add a new 'adaptable' type; and in any case, if you've got the type, why not modify the interface? Well, there may be good reasons why you don't want to modify the class (it's less easy to support backwardly compatible changes if you're using interfaces) or change its type (a HashMap
is not a List
, but can be conveted into one).
当然,当需要添加新的转换类型的时候,仍然会存在修改代码的问题
To solve this problem, there's an abstract class that is used by most parts of Eclipse called PlatformObject
. This implements the IAdaptable
interface on your behalf, without you needing to know about it. Fine, so it implements this interface, but what good is it on its own?
It turns out that the PlatformObject
delegates all of its requests to getAdapter()
to something called IAdapterManager
. One is provided by default for the Platform, and is accesed by calling Platform.getAdapterManager()
. You can think of this as a giant Map
that associates classes with appropriate adapters, and the PlatformObject
's getAdapter()
method as a lookup into this Map
.
PlatformObject
维护了被转换类与及其支持的转换器的关联
Adapting an existing class
The net effect is that it's possible for any PlatformObject
to have a new adapter type dynamically associated with it with no recompilation necessary. This is used in many places to support extensions in the Eclipse workspace.
Let's say that we want to introduce a way of converting a List
of String
s into an XML node. We'd like the XML to look like:
<List> <Entry>First String</Entry> <Entry>Second String</Entry> <Entry>Third String</Entry> </List>
We can't use the List
's toString
method, since it may be used for other purposes. Instead, we can attach a factory to the List
so that when a request for an XML node is requested, a Node
is automatically generated and returned.
So, how do we attach the factory? We need to do three things:
1. Generate a Node
from a List
(the factory)
We use the IAdapterFactory
to wrap our conversion mechanism:
import nu.xom.*; public class NodeListFactory implements IAdapterFactory { /** The supported types that we can adapt to */ private static final Class[] types = { Node.class, };
public Class[] getAdapterList() { return types; }
/** The actual conversion to a Node */ public Object getAdapter(Object list, Class clazz) { if (clazz == Node.class && list instanceof List) { Element root = new Element("List"); Iterator it = list.iterator(); while(it.hasNext()) { Element item = new Element("Entry"); item.appendChild(it.next().toString()); root.appendChild(item); } return root; } else { return null; } } }
// 这个工厂类完成了类型的转换,这样即形成了Adapable--AdapterManager-IAdapterFactory--
2. Register the factory with the Platform
's AdapterManager
We need to pass our factory into the adapter manager, so that when we ask any List
instance for a Node
, it knows to use our factory. The Platform
looks after the IAdapterManager
for us, and it's a relatively simple registration call:
Platform.getAdapterManager().registerAdapters( new NodeListFactory(), List.class);
This asks the platform manager to associate the NodeListFactory
with the List
type. When we ask for an adapter from List
instances, it will consult the factory. It knows that it can obtain a Node
using this factory, because that's what we've defined on the return type in the getAdapterList()
method. In Eclipse, this step is normally performed explicitly at plugin startup, but it can also be done implicitly via the org.eclipse.core.runtime.adapters
extension point.
3. Ask the List
for a Node
This is simply a case of asking the adapter to give us a Node
object:
Node getNodeFrom(IAdaptable list) { Object adaptable = list.getAdapter(Node.class); if (adaptable != null) { Node node = (Node)adaptable; return node; } return null; }
Summary
If you want to be able to add functionality to an existing class at run-time, all we need to do is define a factory that performs the translation on an instance, and then register that factory with the Platform
's AdapterManager
. This functionality is great for registering UI-specific components with non-UI components whilst maintaining a clean seperation between the two, such as used by org.rcpapps.rcpnews.ui and org.rcpapps.rcpnews plugins. In these examples, IPropertySource
in the UI plugin needs to be associated with the data objects in the non-UI plugin. When the UI plugin is initialised, it registers the IPropertySource
with the Platform
, so that when a data object is selected in the navigator, the correct properties are displayed in the properties view.
Obviously, java.util.List
doesn't extend PlatformObject
, so if you want the examples to compile here, you'll need to create a subtype of List
to achieve the effect. However, extending PlatformObject
isn't a requirement:
public class AdaptableList implements IAdaptable, List { public Object getAdapter(Class adapter) { return Platform.getAdapterManager().getAdapter(this, adapter); } private List delegate = new ArrayList(); public int size() { return delegate.size(); } // ... }
The code sample uses the XOM libraries for generating the XML.