1

I've come across the following State Design Pattern which seems extremely logical and simple to implement:

class CeilingFanPullChain
{
    private State m_current_state;

    public CeilingFanPullChain()
    {
        m_current_state = new Off();
    }
    public void set_state(State s)
    {
        m_current_state = s;
    }
    public void pull()
    {
        m_current_state.pull(this);
    }
}

interface State
{
  void pull(CeilingFanPullChain wrapper);
}

class Off implements State
{
    public void pull(CeilingFanPullChain wrapper)
    {
        wrapper.set_state(new Low());
        System.out.println("   low speed");
    }
}

class Low implements State
{
    public void pull(CeilingFanPullChain wrapper)
    {
        wrapper.set_state(new Medium());
        System.out.println("   medium speed");
    }
}

class Medium implements State
{
    public void pull(CeilingFanPullChain wrapper)
    {
        wrapper.set_state(new High());
        System.out.println("   high speed");
    }
}

class High implements State
{
    public void pull(CeilingFanPullChain wrapper)
    {
        wrapper.set_state(new Off());
        System.out.println("   turning off");
    }
}

public class StateDemo
{
    public static void main(String[] args)
    {
        CeilingFanPullChain chain = new CeilingFanPullChain();
        while (true)
        {
            System.out.print("Press ");
            get_line();
            chain.pull();
        }
    }
    static String get_line()
    {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in))
          ;
        String line = null;
        try
        {
            line = in.readLine();
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
        return line;
    }
}

My concern is whether it is sensible to be passing the entire CeilingFanPullChain reference to the state objects. I've always been taught to only pass what is necessary. For example, I may wish to have methods and fields which are of no use to the State objects, so is passing the entire object reference sensible?

Is there another way to re-assign the state in CeilingFanPullChain, or is this not an issue?

  • Not related to your question, but for what it does, that's entirely too much code. Allocating a new object on every change of state is probably not something you're going to want to do if you're after performance, especially in a garbage-collected environment. – Blrfl Jul 4 '16 at 23:32
2

To answer the main question directly : no it is not sensible. Why? Because it exposes the inner working of the class.

As an alternative consider this :

class CeilingFanPullChain {
    private State state;

    public CeilingFanPullChain() {
        state = new Off();
    }

    private void setState(State newState) {
        state = newState;
    }

    public void pull() {
        state.pull(this);
    }

    private interface State {
        void pull(CeilingFanPullChain wrapper);
    }

    private class Off implements State {
        public void pull(CeilingFanPullChain wrapper) {
            wrapper.setState(new Low());
            System.out.println("   low speed");
        }
    }

    private class Low implements State {
        public void pull(CeilingFanPullChain wrapper) {
            wrapper.setState(new Medium());
            System.out.println("   medium speed");
        }
    }

    private class Medium implements State {
        public void pull(CeilingFanPullChain wrapper) {
            wrapper.setState(new High());
            System.out.println("   high speed");
        }
    }

    private class High implements State {
        public void pull(CeilingFanPullChain wrapper) {
            wrapper.setState(new Off());
            System.out.println("   turning off");
        }
    }
}

Note what has changed : State (and the use of the state pattern itself) has been completely hidden from the client code.

  • the State interface and all implementations have become inner classes of CeilingFanPullChain. This hides them from the client, while also allowing them full access to the private members of their containing class.
  • the setPattern() method is private, not visible : clients cannot muck around with the state machine directly.
  • the discussion about whether to apply the state pattern or use a simple switch statement becomes moot. Since it is completely encapsulated, changing from one to the other will not affect the client. You may start out simple, using switch, but as the class grows you may switch to using the state pattern, and the client won't ever notice.
1

While RobertHarvey's answer makes an excellent point -- essentially, that the State pattern is overkill for the example you post and that a simplified version using a switch statement is perfectly acceptable and much more concise in this situation -- I assume that you are positing this as either a learning exercise specifically about the use of the State pattern or as a proxy for a much more complicated problem where the State pattern is appropriate (e.g. because there are multiple behaviours that vary depending on state and keeping them together rather than separating them in different switch statements results in better code organisation).

In this case, there are two possible solutions for the problem you pose:

  • You could create an interface for your state-holding object that contains only the setState() method, and receive that in the argument to your state objects' methods

  • You could have the state objects returning a new state from their methods (rather than have them be void as you do at the moment), at which point they don't need access to the state-holder at all.

  • The code in my post is just an example to demonstrate the pattern. In reality, each "pull" method will be returning an int, and therefore I don't think your second solution is suitable. However, I think your first solution could work! I'll give it a shot. ;) – M-R Jul 5 '16 at 10:02
  • Out of interest, would it be possible to get rid of the wrapper class ("Context") and move it's implementation into the State class? – M-R Jul 5 '16 at 10:04

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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