0

Again, i arrive at some cross road that make me struggle against the correct principles of object composition designs for a ("rich") domain model objects.

Please note that the meaning of composition is the meaning that comes with UML modelling : when someone states that an object contains another object or set of objects, the life of the contained object (or set of object) is attached to (or depends of) the life of the container object.

Let´s show an example so i can directly expose my point of view. Imagine that we have food store that stores "food" request, every request is each element is composed of one or more orders and a order is just described by a food and a quantity. So, let ilustrate this scenerio with an UML diagram --> http://imageshack.us/photo/my-images/546/5lfa.jpg/.

Basically we have three classes : Request, Order and Food. An may i be clear on this, there is no conceivable way to thing in an Order that not belongs to a Request.

After that, i start to code. And i arrive to two different schemes, but before showing the differences between both schemes, let´s write the common class Food.

/**
 * Each object represent diferents types of food.
 */
public enum Food
{
    PIZZA,
    HAMBURGUER,
    ANOTHER_TYPE_OF_FOOD
}

Mmm, yes.. you have right, i stated that is was class... so is a kind of class with well know objects (after all is that Enumeration is about i guess).

First Scheme)

This is the Order Class

Please note the method equals and hashCode

/**
 * An order represent a part of a meal request.
 */
public class Order 
{
    private Food food; // this is one-to-one
    private int  quantity; // the quantity
    private Request request; // this is many-to-one (the ORDER needs to BE WITHIN a Request, if not it can´t exists)

    public Order (Food food,int quantity)
    {
        this.food     = food;
        this.quantity  = quantity;
    }

    public Order (Food food)
    {
        this.food      = food;
        this.quantity  = 0;
    }


    public Food getFood()
    {
        return food;
    }

    public void setFood(Food food)
    {
        this.food = food;
    }

    public int getQuantity()
    {
        return quantity;
    }

    public void setQuantity(int quantity)
    {
        this.quantity = quantity;
    }

    public void incQuantity(int quantity)
    {
        this.quantity += quantity;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (getClass() != obj.getClass())
        {
            return false;
        }
        final Order other = (Order) obj;
        if (this.food != other.food) // must be the same food
        {
            return false;
        }
        if (!Objects.equals(this.request, other.request)) // must belong to the same request
        {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode()
    {
        int hash = 3;
        hash = 37 * hash + (this.food != null ? this.food.hashCode() : 0);
        hash = 37 * hash + Objects.hashCode(this.request);
        return hash;
    }
}

This is the Request Class :

Please note the method placeOrder

/**
 * An instance of Request represents a concrete meal request, belonging to a client, that may include various types of food.
 * NOTE : The request is _composing_ order objects. Order object instantiation and destruction is handled INSIDE this class only.
 */
public class Request
{
    private List<Order> orders; // one-to-many, with COMPOSITION, so the Request OWNS each order (not AGGREGATION) (the ORDER needs to BE WITHIN a Request, if not it can´t exists)

    public Request()
    {
    }

    public void placeOrder (Food food, int quantity) 
    {
        Order order = new Order(food, quantity);
        int index =  orders.indexOf(order);
        if (index != -1)
        {
            Order storedOrder = orders.get(index);
            storedOrder.incQuantity(quantity);
        }
        else 
        {
            orders.add(order);
        }
    }
}

I really start wondering if is right to instantiate a fresh new Order for let the list do the search using the redefined equals and hashCode methods, like i thing is a good java oriented approach at some start point, but here is showing some weak flanks i didn´t expect.

Second Scheme)

This is the Order Class :

Please note, that there is no need of equals and hashcode, because i use a map inside Request class for composition.

/**
 * An order represent a part of a meal request.
 */
public class Order 
{
    private Food food; // this is one-to-one
    private int  quantity; // the quantitfy
    private Request request; // this is many-to-one (the ORDER needs to BE WITHIN a Request, if not it can´t exists)

    public Order (Food food,int quantity)
    {
        this.food     = food;
        this.quantity  = quantity;
    }

    public Order (Food food)
    {
        this.food      = food;
        this.quantity  = 0;
    }


    public Food getFood()
    {
        return food;
    }

    public void setFood(Food food)
    {
        this.food = food;
    }

    public int getQuantity()
    {
        return quantity;
    }

    public void setQuantity(int quantity)
    {
        this.quantity = quantity;
    }

    public void incQuantity(int quantity)
    {
        this.quantity += quantity;
    }

    // No equals and hashcode redefinition needed.
}

This is the Request Class :

Please note, again, the method placeOrder

/**
 * An instance of Request represents a concretre request for food in behalf of a client.
 * NOTE : The request is _composing_ order objects. Order objetc instancition and destruction
 * is handled INSIDE this class only.
 */
public class Request
{
    private Client client; // one-to-one
    private Map<Food,Order> orders; // one-to-many, so the Request OWNS each order (not AGGREGATION) (the ORDER needs to BE WITHIN a Request, if not it can´t exists)

    public Request()
    {
    }

    public void placeOrder (Food food, int quantity) 
    {
        if (orders.containsKey(food)) 
        {
            orders.get(food).incQuantity(quantity);
        }
        else 
        {
            orders.put(food, new Order(food, quantity));
        }
    }

}

Again, i like more this approach, but leaves me with doubts about if it's the right way to do it, and how do i annotate to use later with JPA.

ONE MORE THING. About this issue, i really try to find some good explanations and how to approach this common scenario. But i always fail on that. In Hibernate Quickly, i does´t find a clue. I read until chapter 7. In Hibernate in action, i found something about Bid and Item, but doesn´t approach the problem with composition, instead their show examples using aggregation. (I saw several articles bout problems of managing collections in a composition context, but it doesn´t give a clue on how to solve the weak and strong entities problem that i am stating here)

ONE MORE THING EVEN IMPORTANT

PLEASE: Don´t even thing that this can be resolved this way

public class Request
{

    public void place (Order oder) 
    {
        orders.add(oder);
    }
}

The above approach is using Aggregation, and that way i am delegating the creation and destruction to another upper module that is out of the picture, probably a in a service layer, leaving all of my domain model classes pretty famished, referring to classes with poor encapsulated behavior containing only state. This is something that i DO NOT WANT because i want a model to be rich, in the sense that in includes some business logic, and part of the business logic is about instantiation (that's a true history, let´s face it guys).

So, finally, the question are this:

1) What is the best approach? Obviously i am open to see new angles of view.

2) (More lazily question) Who do i proper annotate the classes with JPA in order to use an ORM?

Well, thanks to all!.

Grettings.

Víctor!

P.D: A Very related question of mine is this!

Community
  • 1
  • 1
Victor
  • 3,841
  • 2
  • 37
  • 63

1 Answers1

0

I recommend a mixed solution if you believe the second scheme is closer to the domain.The key is using List for peristence while using Map for domain.

public class Request {
    private List<Order> orders = new ArrayList<Order>(); //this is easy to persist

    public void placeOrder (Food food, int quantity) {
        //uses orders() everytime you want to manipulate the orders
        final Map<Food, Order> orders = orders(); 
        if (orders.containsKey(food)) {
            orders.get(food).incQuantity(quantity);
        } else {
            orders.put(food, new Order(food, quantity));
        }
    }

    private Map<Food, Order> orders() {
        final Map<Food, Order> map = new HashMap<Food, Order>();
        for (Order order: this.orders) {
            map.put(order.getFood(), order);
        }
        return map;
    }
}

This solution keeps the domain models being least affected by the persistence implementation. The major downside is that this will introduce some overhead for converting the order list to the order map.

Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60