10

I have a class Car which has 2 properties: int price and boolean inStock. It also holds a List of abstract class State (empty class). There are 2 states which can be applied on the car and each is represented by its own class: class Upgrade extends State and class Shipping extends State.

A Car can hold any number of each of the 2 states. The states have the following rules:

  • Upgrade: adds 1 to the price for each state applied to the car after itself.
  • Shipping: if there is at least 1 Shipping state in the list, then inStock is set to false.

For example, starting with price = 1 and inStock = true:

add Shipping s1    --> price: 1, inStock: false
add Upgrade g1     --> price: 1, inStock: false
add Shipping s2    --> price: 2, inStock: false
add Shipping s3    --> price: 3, inStock: false
remove Shipping s2 --> price: 2, inStock: false
remove Upgrade g1  --> price: 1, inStock: false
remove Shipping s1 --> price: 1, inStock: false
remove Shipping s3 --> price: 1, inStock: true

I was thinking about the observer pattern where each add and remove operations notify observers. I had something like this in mind, but it doesn't obey the rules I posed:

abstract class State implements Observer {

    public abstract void update();
}

class Car extends Observable {

    List<State> states = new ArrayList<>();
    int price = 100;
    boolean inStock = true;

    void addState(State state) {

        if (states.add(state)) {
            addObserver(state);
            setChanged();
            notifyObservers();
        }
    }

    void removeState(State state) {

        if (states.remove(state)) {
            deleteObserver(state);
            setChanged();
            notifyObservers();
        }
    }
}

class Upgrade extends State {

    @Override
    public void update(Observable o, Object arg) {

        Car c = (Car) o;
        int bonus = c.states.size() - c.states.indexOf(this) - 1;
        c.price += bonus;
        System.out.println(c.inStock + " " + c.price);
    }
}

class Shipping extends State {

    @Override
    public void update(Observable o, Object arg) {

        Car c = (Car) o;
        c.inStock = false;
        System.out.println(c.inStock + " " + c.price);
    }
}

Obviously, this doesn't work. When a Shipping is removed, something has to check if there is another state setting inStock to false, so a removal of Shipping can't just inStock = true. Upgrade increases price at each call. I then added constants for the default values and attempted a recalculation based on those.

I am by no means trying to impose any pattern, I'm just trying to find a solution for the above requirements. Note that in practice Car contains many properties and there are many states that can be applied in this manner. I thought about a few ways to do this:

  1. Since each observer receives Car, it can look at all the other observers currently registered and make a change based on that. I don't know if it's smart to entangle observers like this.
  2. When an observer is added or removed in Car, there will be a recalculation. However, this recalculation will have to be done on all observers regardless of the one which was just added/removed.
  3. Have an external "manager" class which will call the add and remove methods and do the recalculation.

What is a good design pattern to implement the described behavior and how would it work?

2 Answers2

2

The observers would work great if you factor the system differently. Instead of making states themselves to be observers, you could make 2 new classes to be "state change observers": one observer would update "price", another one would update "inStock". In this way they would be independent if you don't have rules for price depending on inStock or vice versa, i.e. if everything could be calculated by just looking at the state changes. This technique is called "event sourcing" (for example see - https://ookami86.github.io/event-sourcing-in-practice/ ). It's a pattern in programming that has some notable applications.

Answering to a more general question, sometimes you really have dependencies between observers. For example, you might want one observer to react before another. In such case it's usually possible to make a custom implementation of Observable class to deal with ordering or dependencies.

1

I ended up going with option 3 - using an external manager. The manager is responsible for adding and removing States from Cars and for notifying the observers when these changes happen.

Here is how I modified the code. I removed the Observable/Observer of the JDK because I'm doing my own implementation.

Each State keeps a reference to the Car its applied to.

abstract class State {

    Car car;

    State(Card car) { this.car = car; }

    public abstract void update();
}

class Upgrade extends State {

    @Override
    public void update() {

        int bonus = car.states.size() - car.states.indexOf(this) - 1;
        car.price += bonus;
        System.out.println(car.inStock + " " + car.price);
    }
}

class Shipping extends State {

    @Override
    public void update() {

        car.inStock = false;
        System.out.println(car.inStock + " " + car.price);
    }
}

Car only holds its state (to avoid confusion: properties) and does not handle adding and removing States:

class Car extends Observable {

    List<State> states = new ArrayList<>();
    int price = 100;
    boolean inStock = true;
}

Here is the manager. It has overtook Cars (the observable) job of managing its States (observers).

class StatesManager {

    public void addState(Card car, State state) {

        car.states.add(state);
        for (State state : car. states)
            state.update;
    }

    public void removeState(Card car, State state) {

        car.states.remove(state);
        for (State state : car. states)
            state.update;
    }
}

A few things to keep in mind:

  • All observers are notified about every change. A more clever event distribution scheme can eliminate unneeded calls to observers' update method.
  • The observers might want to expose more "update"-like methods for different occasions. Just as an example, they can split the current update method to updateOnAdd and updateOnRemove if they are interested only in one of these changes. Then the addState and removeState methods would be updated accordingly. Along with the previous point this approach can end up as a robust, extensible and flexible mechanism.
  • I did not specify what gives the instruction to add and remove States and when that happens as it is not important for the question. However, in the case of this answer, there is the following point to consider. Since State now must be created with its Car (no empty constructor exposed) prior to calling the manager's method, the addState and removeState methods do not need to take a Car and can just read it from state.car.
  • The observers are notified in order of registration on the observable by default. A different order can be specified.