Composition

From cdd
Jump to: navigation, search

Composition is a great technique used to construct more complex objects in object oriented languages, also known as aggregate in Domain-driven Design:


Traditional-object-composition.png


Composition is often a better alternative than inheritance mainly because it's more modular and lacks many of the quirks associated with inheritance. Composition is the way this type of stateful objects is constructed in CDD. The main difference compared to traditional object orientation is that each behaviour representation, with all its sub behaviours, can be specialized for different contexts:


CDD-object-composition.png


.....Let's start by composing a person state object with all its parts:

public class AddressState {
    String streetName;
    int streetNumber;
    int zipcode;
    String city;
}
 
public class PersonState {
    String givenName;
    String surname;
    int heightCm;
    int weightKg;
    @Optional AddressState address;
    @Optional ImmutableList<PersonState> children;
}

...make sure a Person and a PersonCreator is also created (have a look at the other examples).


...let's se how the code behaves so far by executing:

        Person person = createPerson().givenName("Anders").surname("Andersson").heightCm(179).weightKg(75)
                .withAddress(createAddress().streetName("First street").streetNumber(2).zipcode(123).city("Stockholm"))
                .withChildren(createPersonList(
                                createPerson().givenName("Carl").surname("Andersson").heightCm(110).weightKg(20),
                                createPerson().givenName("Eric").surname("Andersson").heightCm(130).weightKg(30))).asPerson();
 
System.out.println(person);

...output:

Person{givenName='Anders', surname='Andersson', heightCm=179, weightKg=75,
  address={streetName='First street', streetNumber=2, zipcode=123, city='Stockholm'},
  children=[{givenName='Carl', surname='Andersson', heightCm=110, weightKg=20, address=null, children=[]},
            {givenName='Eric', surname='Andersson', heightCm=130, weightKg=30, address=null, children=[]}]}


The attributes address and children was marked as optional and therefore populated by builder with-methods.


Let's add some behaviour to address:

package com.myproject;
 
import static com.myproject.AddressCreator.AddressBehaviour;
 
public class Address extends AddressBehaviour {
    public Address(AddressState state) {
        super(state);
    }
 
    // Ullared is a small town in Sweden.
    public boolean isRegisteredInUllared() {
        return state.getZipcode() >= 31109 && state.getZipcode() <= 31185;
    }
}


Also add some behaviour to Person:

package com.myproject;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
 
import static com.myproject.PersonCreator.PersonBehaviour;
 
public class Person extends PersonBehaviour {
    private final Address address;
 
    public Person(PersonState state) {
        super(state);
        address = new Address(state.address);
    }
 
    public String name() {
        return state.givenName + " " + state.surname;
    }
 
    public List<String> childrenName() {
        List<String> names = new ArrayList<String>();
        for (PersonState child : state.children) {
            names.add(new Person(child).name());
        }
        return names;
    }
 
    public boolean isRegisteredInUllared() {
        return address.isRegisteredInUllared();
    }
 
    /**
     * @return body mass index with one decimal.
     */
    public double calculateBmi() {
        double heightInMeter = state.getHeightCm() / 100.0;
        double bmi = state.getWeightKg() / (heightInMeter * heightInMeter);
        return new BigDecimal(bmi).setScale(1, RoundingMode.HALF_EVEN).doubleValue();
    }
 
    public boolean hasNormalWeight() {
        double bmi = calculateBmi();
        return bmi >= 18.5 && bmi <= 24.9;
    }
}


...if executed:

package com.myproject.behaviour;
 
public class Main {
    public static void main(String[] args) {
        Person person = createPerson().givenName("Anders").surname("Andersson").heightCm(179).weightKg(75)
                .withAddress(createAddress().streetName("First street").streetNumber(2).zipcode(123).city("Stockholm"))
                .withChildren(createPersonList(
                                createPerson().givenName("Carl").surname("Andersson").heightCm(110).weightKg(20),
                                createPerson().givenName("Eric").surname("Andersson").heightCm(130).weightKg(30))).asPerson();
 
        System.out.println("Name: " + person.name());
        System.out.println("Body Mass Index: " + person.calculateBmi());
        System.out.println("Has normal weight: " + person.hasNormalWeight());
        System.out.println("Registered in Ullared: " + person.isRegisteredInUllared());
        System.out.println("Children: " + person.childrenName());
    }
}

...we get the output:

Name: Anders Andersson
Body Mass Index: 23.4
Has normal weight: true
Registered in Ullared: false
Children: [Carl Andersson, Eric Andersson]


< Back          Next >