Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

The Replace Inheritance with Delegation refactoring (sometimes known as Replace Implementation Inheritance With Composition) provides a systematic way to replace inheritance hierarchies.

Inheritance hierarchies allow system designers to elegantly express relationships between different types of objects in a system. However, such hierarchies can be difficult to implement and refactor. Many developers favour restricting the use of inheritance hierarchies to very simple scenarios. If you started out using inheritance hierarchies (perhaps you had a simple scenario to start with) but are now finding the inheritance hierarchy is now getting in the way, apply this pattern.

Property Example

Suppose we have the following property-centric class:

Code Block
class Person {
    def name
    def age
    def nationality
}

We might have a related class such as this:

Code Block
class StaffMemberUsingInheritance extends Person {
    def salary
}

For this simple case, we can stop here. For more complicated cases, the inheritance might start getting in the way. Here is how you can remove it using traditional delegation:

Code Block
class StaffMemberUsingDelegation {
    private delegate = new Person()
    def salary
    def getName() {
        delegate.name
    }
    def setName(name) {
        delegate.name = name
    }
    def getAge() {
        delegate.age
    }
    def setAge(age) {
        delegate.age = age
    }
    def getNationality() {
        delegate.nationality
    }
    def setNationality(nationality) {
        delegate.nationality = nationality
    }
}

It looks like we have greatly increased the size of our code. This is because the earlier example was making using of Groovy's compact property notation which we can't use in this case. We should notice however, that most of this code is fairly boiler-plate in style.

Even though this example is not too bad, it becomes annoying to have to read, write and maintain this boiler-plate code. Instead, we can make use of Groovy's Meta-Programming capabilities as follows:

Code Block
class StaffMemberUsingMOP {
    private delegate = new Person()
    private hasLocalProperty(name) {
        metaClass.properties.collect{ it.name }.contains(name)
    }
    def salary
    StaffMemberUsingMOP(Map map) {
        map.each{ k, v -> setProperty(k, v) }
    }
    void setProperty(String name, value) {
        if (hasLocalProperty(name)) this.@"$name" = value 
        else delegate.setProperty(name, value)
    }
    def getProperty(String name) {
        if (hasLocalProperty(name)) return this.@"$name"
        else return delegate.getProperty(name)
    }
}

We can use the above classes with this script code:

Code Block
def p1 = new StaffMemberUsingInheritance(name:'Tom', age:20, nationality:'French', salary:1000)
def p2 = new StaffMemberUsingDelegation(name:'Dick', age:25, nationality:'German', salary:1100)
def p3 = new StaffMemberUsingMOP(name:'Harry', age:30, nationality:'Dutch', salary:1200)
describe(p1)
describe(p2)
describe(p3)

With the result being:

Code Block
Tom has a salary of 1000
Dick has a salary of 1100
Harry has a salary of 1200

Method Example

The above example focussed on classes that were very data centric and were implemented using properties. Let's consider the a similar example but this time with classes which are method centric.

First, we define a Person class.

Code Block
class Person {
    private name
    private age
    Person(name, age) {
        this.name = name
        this.age = age
    }
    def haveBirthday() { age++ }
    def describe() { "$name is $age years old" }
}

We can use inheritance to define a staff member class as follows:

Code Block
class StaffMemberUsingInheritance extends Person {
    private salary
    StaffMemberUsingInheritance(name, age, salary) {
        super(name, age)
        this.salary = salary
    }
    def describe() {
        super.describe() + " and has a salary of $salary"
    }
}

This works well here, but in complex systems the inheritance might start to complicate our system. An alternative way to implement the functionality using traditional delegation is shown here:

Code Block
class StaffMemberUsingDelegation {
    private delegate
    private salary
    StaffMemberUsingDelegation(name, age, salary) {
        delegate = new Person(name, age)
        this.salary = salary
    }
    def haveBirthday() {
        delegate.haveBirthday()
    }
    def describe() {
        delegate.describe() + " and has a salary of $salary"
    }
}

The pattern here is simple, for each method in the delegate class that we want available in the staff member class, we create a method that explicitly calls the delegate. Simple but this is boiler-plate code that needs to change whenever we change the underlying classes.

So, as an alternative, we can use Groovy's Meta Object Programming facilities to auto delegate methods using invokeMethod:

Code Block
class StaffMemberUsingMOP {
    private delegate
    private salary
    StaffMemberUsingMOP(name, age, salary) {
        delegate = new Person(name, age)
        this.salary = salary
    }
    def invokeMethod(String name, args) {
        delegate.invokeMethod(name, args)
    }
    def describe() {
        delegate.describe() + " and has a salary of $salary"
    }
}

The result in this case looks like we didn't save much code at all. If however, there were lots of methods from the delegate that we needed to use, this one wouldn't grow in size, whereas the previous approach would become larger. Also, this version will require less maintenance as we don't need to explicitly change it if the Person class changes over time. Note that because the describe method didn't follow the boiler-plate approach of simple delegation (because it needed extra logic), we are still required to implement that method manually.

This script code shows the various classes described above in action:

Code Block
def p1 = new StaffMemberUsingInheritance('Tom', 20, 1000)
def p2 = new StaffMemberUsingDelegation('Dick', 25, 1100)
def p3 = new StaffMemberUsingMOP('Harry', 30, 1200)
p1.haveBirthday()
println p1.describe()
p2.haveBirthday()
println p2.describe()
p3.haveBirthday()
println p3.describe()

Which results in the following output:

Code Block
Tom is 21 years old and has a salary of 1000
Dick is 26 years old and has a salary of 1100
Harry is 31 years old and has a salary of 1200

Further Information on this Pattern