Search code examples
javaoopdesign-patternsencapsulationstate-pattern

Broken encapsulation in State pattern in Java


I have implemented state pattern for my socket library in Java. I simplified code to reduce size and increase readability just to demonstrate the problem. I don't know how to encapsulate using of Socket class by states classes. I've written comments to better understanding the problem. So, there is an example code:

package com.test;

enum ConnectionAttemptResult {
    ConnectionAttemptStarted,
    AlreadyInProgress,
    AlreadyConnected,
    UnableToCreateSocket,
    UnableToConnectSocket,
    UnableToCreateReadEvent
}

class SocketAddress {
    static SocketAddress LocalhostPort(int port) {
        // Code removed.
        return new SocketAddress();
    }
}

class Socket {
    private State currentState;

    Socket() {
        this.currentState = new NotActiveState(this);
    }

    public ConnectionAttemptResult Connect(SocketAddress remoteAddress) {
        return this.currentState.Connect(remoteAddress);
    }

    // Socket class is external and will be used by user.
    // So further methods must be private because they are internal.
    // Calling these methods by a user may broke class functionality.
    // But I can't make them private because then states couldn't access them.
    public void SwitchState(State newState) {
        this.currentState = newState;
    }

    // I removed a lot of code of this class to simplify the code.
    public boolean CreateSocket() {
        // Code removed.
        return true;
    }

    public boolean ConnectSocket(SocketAddress remoteAddress) {
        // Code removed.
        return true;
    }

    public boolean CreateReadEvent() {
        // Code removed.
        return true;
    }
}

abstract class State {
    protected Socket socket;

    State(Socket socket) {
        this.socket = socket;
    }

    public abstract ConnectionAttemptResult Connect(SocketAddress remoteAddress);
}

class NotActiveState extends State {
    NotActiveState(Socket socket) {
        super(socket);
    }

    @Override
    public ConnectionAttemptResult Connect(SocketAddress remoteAddress) {
        if (!socket.CreateSocket())
            return ConnectionAttemptResult.UnableToCreateSocket;

        if (!socket.ConnectSocket(remoteAddress))
            return ConnectionAttemptResult.UnableToConnectSocket;

        if (!socket.CreateReadEvent())
            return ConnectionAttemptResult.UnableToCreateReadEvent;

        socket.SwitchState(new ConnectingState(socket));
        return ConnectionAttemptResult.ConnectionAttemptStarted;
    }
}

class ConnectingState extends State {
    ConnectingState(Socket socket) {
        super(socket);
    }

    @Override
    public ConnectionAttemptResult Connect(SocketAddress remoteAddress) {
        return ConnectionAttemptResult.AlreadyInProgress;
    }
}

class ConnectedState extends State {
    ConnectedState(Socket socket) {
        super(socket);
    }

    @Override
    public ConnectionAttemptResult Connect(SocketAddress remoteAddress) {
        return ConnectionAttemptResult.AlreadyConnected;
    }
}

class Main {
    public static void main(String[] args) {
        Socket socket = new Socket();
        socket.Connect(SocketAddress.LocalhostPort(9898));

        // But I also can call internal methods and break the class functionality, because now they are public.
        socket.CreateSocket();
        socket.CreateReadEvent();
    }
}

Where is my mistake? How usually developers implement this pattern saving encapsulation? Hope for your help! Thanks in advance!


Solution

  • Well, you could make state classes nested private classes and they would have access to internals of the containing class. As we can see the TransitiveState can access the SomethingWithState#previousState private variable. I think static nested classes could also access the private members of the enclosing class but you'd have to pass them the SomethingWithState instance.

    The states are an implementation detail so they do not and probably should not be exposed externally.

    E.g.

    public class StatePatternExample {
        public static void main(String[] args) {
            SomethingWithState something = new SomethingWithState();
            something.next();
            something.next();
        }
        
        private static class SomethingWithState {
            private State state;
            private State previousState;
            
            SomethingWithState() {
                this.previousState = null;
                this.state = new InitialState();
            }
            
            void next() {
                State next = state.next();
                System.out.println(state.name() + " --> " + next.name());
                previousState = state;
                state = next;
            }
            
            private interface State {
                State next();
                String name();
            }
            
            private class InitialState implements State {
                public State next() {
                    
                    return new TransitiveState();
                }
                public String name() { return "Initial"; }
            }
            
            private class TransitiveState implements State {
                public State next() {
                    System.out.println("Access private previousState from TransitiveState: was " + previousState.name());
                    
                    return new FinalState();
                }
                public String name() { return "Transitive"; }
            }
            
            private class FinalState implements State {
                public State next() {
                    throw new IllegalStateException("Already in final state.");
                }
                public String name() { return "Final"; }
            }
        }
    }