In my last installment, I showed how—by making a domain class implement IPropertySource
and (naturally) implementing the methods required by that interface—you can expose properties from a domain class that can be viewed and modified by the Properties view. In that posting, I suggested that implementing IPropertySource
introduces a tight coupling between the model and view and hinted that such a tight coupling was a bad thing.
Tight coupling is bad because it tends to make things less flexible. Sure, we can look at the properties of our domain object, but what happens when we want to participate in other interactions? Do we just implement another interface? And another? Tight coupling makes reuse harder as well. Tightly coupling our domain class with the IPropertySource
interface makes it so that our domain class can’t exist without that interface (and all the other types packaged along with it, plus those bits referenced by all those types, …).
Eclipse provides an adapter framework that can be used to solve this problem by decoupling the domain class from the view-specific code required to make the Properties view work.
The first step is to remove the IPropertySource
behaviour from the domain class:
... 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) { if (adapter == IPropertySource.class) return new PersonPropertySource(this); return null; } // Getter and setter methods follow... ... }
We move the IPropertySource
behaviour to the PersonPropertySource
class:
... public class PersonPropertySource implements IPropertySource { private final Person person; public PersonPropertySource(Person person) { this.person = person; } public Object getEditableValue() { return this; } public IPropertyDescriptor[] getPropertyDescriptors() { return new IPropertyDescriptor[] { new TextPropertyDescriptor("name", "Name"), new TextPropertyDescriptor("street", "Street"), new TextPropertyDescriptor("city", "City") }; } public Object getPropertyValue(Object id) { if ("name".equals(id)) return person.getName(); else if ("street".equals(id)) return person.getStreet(); else if ("city".equals(id)) return person.getCity(); return null; } public boolean isPropertySet(Object id) { return false; } public void resetPropertyValue(Object id) { } public void setPropertyValue(Object id, Object value) { if ("name".equals(id)) person.setName((String)value); else if ("street".equals(id)) person.setStreet((String)value); else if ("city".equals(id)) person.setCity((String)value); } }
The Property view goes through a few steps to sort out how it’s going to display properties. First, it determines whether or not the selected object implements the IPropertySource
interface. If it does (as it did in my previous entry), it uses the selected object directly (after casting it to IPropertySource
). If that check fails, the Property view then determines whether or not the selected object implements the IAdaptable
interface (highlighted in the Person class). If the selected object is adaptable, it is asked—via the getAdapter
method—for an adapter with the IPropertySource
type. The getAdapter
method either returns an object of the appropriate type or null
. If the method returns an adapter, it is used by the Property view to gather properties. Our implementation of adapter fits this bill and so is used.
The astute reader will notice that this really doesn’t do very much to actually weaken the coupling between the domain class an IPropertySource. In fact, the coupling is just as strong. Even worse, we’ve actually introduced a tight coupling to another type (IAdaptable
). We’ll fix this problem in the next installment…