In my last post on the topic, I hinted that adapters can be used to loosen the coupling in your code and then showed how you could use adapters to add even more coupling in your code. Originally, I had a domain class, Person
, that knew how to provide information to the Properties view by virtue of implementing the IPropertySource
interface. Then, I “decoupled” the domain class from the interface by instead implementing a getAdapter
method that returned an instance of a different class when asked for an adapter of the type IPropertySource
. I put “decoupling” in quotes, because this step really does nothing to decrease coupling; all it does is move the code somewhere else, the Person
class is still just as tightly coupled to the IPropertySource
interface. In fact, it is now also coupled to the IAdaptable
interface.
Take a few minutes to familiarize yourself with the previous steps. They’re here and here. I’ll wait…
The next step is to completely decouple the domain class from IPropertySource
. To do this, I can change the getAdapter
method:
public class Person implements IAdaptable { private String name; private Object street; private Object city; public Person(String name) { this.name = name; this.street = ""; this.city = ""; } public Object getAdapter(Class adapter) { return AdapterManager.getDefault().getAdapter(this, adapter); } ... }
Previously, the getAdapter
method checked the type of the desired adapter and created an appropriate instance (if possible) itself. Now, the method makes a call to the AdapterManager
, which can take care of figuring out how to adapt the instance.
For this to work, the adapter manager needs to be told how to adapt the type. This can be done declaratively through the plugin.xml
file:
<plugin> <extension point="org.eclipse.core.runtime.adapters"> <factory adaptableType="org.eclipse.example.Person" class="org.eclipse.example.adapters.PersonPropertiesSourceAdapterFactory"> <adapter type="org.eclipse.ui.views.properties.IPropertySource"> </adapter> </factory> </extension> </plugin>
This extension defines an adapter for instances of the org.eclipse.example.Person
class. When asked to adapt to the org.eclipse.ui.views.properties.IPropertySource
, the PersonPropertiesSourceAdapterFactory
should be used. This factory class is defined as such:
public class PersonPropertiesSourceAdapterFactory implements IAdapterFactory { public Object getAdapter(Object adaptableObject, Class adapterType) { if (adapterType == IPropertySource.class) return new PersonPropertySource((Person)adaptableObject); return null; } public Class[] getAdapterList() { return new Class[] {IPropertySource.class}; } }
The getAdapter
method does the heavy lifting and creates the adapter. The getAdapterList
returns a list of the types of adapters the factory can create.
Adapter factories can also be registered programmatically using APIs on the AdapterManager
class.
With the adapters all safely registered, we can make one more change to our code. When adapters are used, they are usually used in three steps::
- If the object implements the required interface, use the object
- If the object implements
IAdaptable
, call thegetAdapter
method an use the returned object; if something other thannull
is answered, use the returned value - Get the
AdapterManager
to try and adapt the object
The final step actually makes the current implementation of getAdapter
in the Person
class redundant. We can simply remove it, and remove the reference to the IAdaptable
interface from the class. That is, the Person
class simplifies to:
public class Person { private String name; private Object street; private Object city; public Person(String name) { this.name = name; this.street = ""; this.city = ""; } ... }
Voila! the domain class is totally decoupled from the adapter type. Instances of the person class, when selected in your favourite view will populate the Properties view.
There’s still a couple of issues to deal with. But this entry’s getting a little long, so we’ll talk about them next time (and I’ll post some example code).