9

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?

  • 1
    The point of abstracting out State into its own class is to separate out Logic that does not interact with each other, simplifying the code. However your business logic dictates that their Logic is linked, and hence, you have to plumb back in the link, resulting in this God awful mess. It's not the observer pattern that is the problem here, it's the Role pattern you are applying with State. – ArTs Mar 27 '16 at 7:00
  • How would you solve the problem if you were doing it by hand? – James Youngman Jul 18 '16 at 8:52
  • @JamesYoungman I ended up using my 3rd option - an external manager. The rules your write on paper for this case are simple, but the options the language gives you to implement them are limited in this case. Hence the need for a design pattern. Thinking about "how would you do it by hand" works more for algorithms than for applying a clear set of rules. – user1803551 Jul 18 '16 at 14:22
  • @user1803551 You chose well. – Tulains Córdova Aug 18 '16 at 16:47
  • Have one event handler for all events. This handler is simply the entry point for re-calculating the object's complete state. It's a archtypical problem. You see it manifest when working a form "top to bottom, left to right" - everything is A-OK, but then changing something in the middle does not properly recalculate. If you ever end up asking "how can I guarantee event handling order?", now you know what you have to do. – radarbob Mar 19 '17 at 21:48
1

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.

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.