Creator

From cdd
Jump to: navigation, search

CDD "objects" are divided into three parts: creator, state and behaviour. Let's explain how the creator works with an example.

Add these two classes:


public class PersonState {
    public int ssn;
    public String name;
}
 
public class PersonCreator {
    PersonState state;
}


...regenerate and add Person (extending PersonBehaviour will help us with the hashCode/equals/toString methods):

import static com.myproject.PersonCreator.PersonBehaviour;
 
public class Person extends PersonBehaviour {
    // Immutable state
    public Person(PersonState state) {
        super(state);
    }
 
    public boolean isTheAuthor() {
        return state.name.toLowerCase().equals("joakim tengstrand");
    }
}


...add TextPerson:

package com.myproject;
 
import static com.myproject.PersonCreator.PersonMutableBehaviour;
import static com.myproject.PersonState.PersonMutableState;
 
public class TextPerson extends PersonMutableBehaviour {
    private final PersonMutableState state;
 
    // Mutable state
    public TextPerson(PersonMutableState state) {
        super(state);
        this.state = state;
    }
 
    public String text() {
        return state.ssn + ";" + state.name + ";";
    }
}


...and two methods to PersonCreator:

public class PersonCreator ...  {
    ...
 
    public Person asPerson() {
        return new Person(state.asImmutable());
    }
    public Person asTextPerson() {
        return new TextPerson(state);
    }
 
    ...
}


After regeneration, the code looks something like this (some code has been removed and replaced with "..."):

@Creator
public class PersonCreator implements PersonCreatorMaker {
    private final PersonMutableState state;
 
    public Person asPerson() { ... }
    public Person asTextPerson() { ... }
 
    // ===== Generated code =====
 
    public static PersonFactory createPerson() { ... }
    public static PersonCreator createPersonFromMap(Map person) { ... }
    public static PersonBuilder buildPerson() { ... }
    public static PersonBuilder buildPersonFromMap(Map person) { ... }
    public static PersonStringBuilder buildPersonFromStrings() { ... }
    public static PersonListBuilder createPersonList(PersonCreator... creators) { ... }
    public static PersonSetBuilder createPersonSet(PersonCreator... creators) { ... }
    public static PersonMapBuilder createPersonMap(PersonMapEntryBuilder... builders) { ... }
    public static PersonMapEntryBuilder createPersonEntry(Object key, PersonCreator creator) { ... }
    public static PersonMapEntryBuilder createPersonEntry(Object key, PersonBuilder builder) { ... }
 
    public PersonCreator(PersonMutableState state) { this.state = state; }
 
    public class PersonFactory { ... }
 
    public PersonState asState() { ... }
    public PersonMutableState asMutableState() { ... }
    public Map asMap() { ... }
    public PersonStringState asStringState() { ... }
    public PersonStringState asStringState(StringStateConverter stateConverter) { ... }
 
    public void assertIsValid() { ... }
    public boolean isValid() { ... }
    public ValidationErrors validate() { ... }
 
    @Override public int hashCode() { ... }
    @Override public boolean equals(Object that) { ... }
    @Override public String toString() { ... }
 
    public static class PersonBehaviour { ... }
    public static class PersonMutableBehaviour { ... }
    public static class PersonBuilder implements PersonCreatorMaker { ... }
    public static class PersonStringBuilder { ... }
    public static class PersonListBuilder implements Iterable<PersonCreator> { ... }
    public static class PersonSetBuilder implements Iterable<PersonCreator> { ... }
    public static class PersonMapEntryBuilder { ... }
    public static class PersonMapBuilder { ... }
}
 
interface PersonCreatorMaker { ... }


As you can see, a lot of convenient methods has been generated (and a lot of code that we rather would like to hide, but remember this is a compromise!), like:

  1. Methods that creates and builds different representations of person state like immutable state, mutable state, map state and string state.
  2. Validation methods.
  3. Hash code, equals and to string.
  4. Methods for creating sets, lists and maps.


Let's see how this can look like in practice:


package com.myproject;
 
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
 
import java.util.Map;
 
import static com.myproject.PersonCreator.*;
import static com.myproject.PersonState.PersonMutableState;
import static com.myproject.PersonState.PersonStringState;
 
public class Main {
 
    public static void main(String[] args) {
 
        // Four kind of states: immutable, mutable, string...
        PersonState immutableState = createPerson().ssn(111).name("Adam").asState();
        PersonMutableState mutableState = createPerson().ssn(222).name("Bert").asMutableState();
        PersonStringState stringState = createPerson().ssn(333).name("Cecilia").asStringState();
 
        // ...and map!
        Map personMap = createPerson().ssn(444).name("David").asMap();
        Person personFromMap = createPersonFromMap(personMap).asPerson();
 
        // Two contexts "Person" and "TextPerson".
        Person person = createPerson().ssn(555).name("Eric").asPerson();
        TextPerson textPerson = createPerson().ssn(666).name("Fredric").asTextPerson();
 
        // Support for builders:
        Person personViaBuilder = buildPerson().withSsn(777).withName("Gustav").asPerson();
        PersonState stateViaStringBuilder = buildPersonFromStrings().withSsn("888").withName("Henrik").asState();
 
        // Support for sets, list and maps.
        ImmutableSet<Person> setOfPersons = createPersonSet(
                createPerson().ssn(1001).name("Ingvar"),
                createPerson().ssn(1002).name("Jacob")
        ).asPersonSet();
 
        ImmutableList<TextPerson> listOfTextPersons = createPersonList(
                createPerson().ssn(2001).name("Karl"),
                createPerson().ssn(2002).name("Ludvig")
        ).asTextPersonList();
 
        Map<String,PersonMutableState> mapOfMutableStates = createPersonMap(
                createPersonEntry("p1", createPerson().ssn(1).name("x")),
                createPersonEntry("p2", createPerson().ssn(2).name("y")),
                createPersonEntry("p3", createPerson().ssn(3).name("z"))
        ).asMutableStateMap();
 
        PersonMutableState person3 = mapOfMutableStates.get("p3");
 
        System.out.println("immutableState: " + immutableState);
        System.out.println("mutableState: " + mutableState);
        System.out.println("stringState: " + stringState);
 
        System.out.println("personMap: " + personMap);
        System.out.println("personFromMap: " + personFromMap);
 
        System.out.println("person: " + person);
        System.out.println("textPerson: " + textPerson);
 
        System.out.println("personViaBuilder: " + personViaBuilder);
        System.out.println("stateViaStringBuilder: " + stateViaStringBuilder);
 
        System.out.println("setOfPersons:" + setOfPersons);
        System.out.println("listOfTextPersons:" + listOfTextPersons);
        System.out.println("mapOfMutableStates:" + mapOfMutableStates);
 
        System.out.println("person3: " + person3);
    }
}


If executed, we get:

immutableState: {ssn=111, name="Adam"}
mutableState: {ssn=222, name="Bert"}
stringState: {ssn="333", name="Cecilia"}
personMap: {ssn=444, name=David}
personFromMap: Person{ssn=444, name="David"}
person: Person{ssn=555, name="Eric"}
textPerson: TextPerson{ssn=666, name="Fredric"}
personViaBuilder: Person{ssn=777, name="Gustav"}
stateViaStringBuilder: {ssn=888, name="Henrik"}
setOfPersons:[Person{ssn=1001, name="Ingvar"}, Person{ssn=1002, name="Jacob"}]
listOfTextPersons:[TextPerson{ssn=2001, name="Karl"}, TextPerson{ssn=2002, name="Ludvig"}]
mapOfMutableStates:{p3={ssn=3, name="z"}, p2={ssn=2, name="y"}, p1={ssn=1, name="x"}}
person3: {ssn=3, name="z"}


CDD is a very composable and powerful "tool" that "extends" object orientation (Java in this case) and simplifies writing clean code!

Dealing with several creators

If you have more than one creator for a given state then (in some situations) you need to mark one of them as the main creator:

@Creator(main = true)
public class PersonCreator implements PersonCreatorMaker {
    private final PersonMutableState state;
 
    ...
 
}

Dependency management between contexts

The definition of state:

public class PersonState {
    public int ssn;
    public String name;
}


...and the creator:

public class PersonCreator implements PersonCreatorMaker {
    private final PersonMutableState state;
 
    public Person asPerson() {
        return new Person(state.asImmutable());
    }
 
    public TextPerson asTextPerson() {
        return new TextPerson(state);
    }
    ...
}


...allows us to write:

Person person = createPerson().ssn(123).name("Carl").asPerson()
TextPerson textPerson = createPerson().ssn(123).name("Carl").asTextPerson()


Assume that we don't want the "person" context to know anything about the "text" context, then we need to remove the asTextPerson from PersonCreator and instead add a separate creator for the "text" context:


package com.myproject;
 
import static com.myproject.PersonState.PersonMutableState;
 
public class TextPersonCreator {
    PersonMutableState state;
 
    public TextPerson asTextPerson() {
        return new TextPerson(state);
    }
}


...regenerate and we can now write (remember to include a static import for TextPersonCreator.createTextPerson):

TextPerson person = createTextPerson().ssn(123).name("Carl").asTextPerson();


< Back          Next >