Search code examples
javaoopdesign-patternssolid-principlesliskov-substitution-principle

Did I violate the LSP principle in this example?


I have this code that implements 2 types of doors. One door contains a lock and the other one does not.

The Door interface is a no brainer:

public interface Door {
    void open();
    void close();
}

Then I have the implementations: LockedDoor and RegularDoor

public class LockedDoor implements Door {
    private Lock lock;
    private boolean isOpen;

    @Override
    public void open() {
        if(!lock.isLocked()) {
            this.isOpen = true;
        }
    }

    @Override
    public void close() {
        this.isOpen = false;
    }
}

public class RegularDoor implements Door {
    private boolean isOpen;

    @Override
    public void open() {
        isOpen = true;
    }

    @Override
    public void close() {
        isOpen = false;
    }
}

As you can see, the LockedDoor's open function will open the door only if the lock is unlocked.
You can unlock the lock by receiving it from LockedDoor and calling it's unlock function.

Is it a violation of Liskov Substitution Principle?
If it is, what will be a good alternative?


Solution

  • It's a bit hard to answer this question, as your interface for Door seems incomplete in the sense that it's unclear what open() and close() are supposed to do. Let's clear it up by adding an isOpen() method, and defining that once open() is called, a subsequent call to isOpen() should return true (and I'm purposely ignoring the question of what happens if you attempt to open and already open door, just for the sake of brevity).

    In this case, you are definitely violating the LSP principle - if you attempt to open a locked door you'd fail, and the door would remain closed.

    One way to solve this issue is to add a return value to the open() and close() methods so they can report back whether or not the operation succeeded:

    public interface Door {
        /**
         * Checks if the door is open.
         * @return {@code true} if the door is open, {@code false} if not.
        boolean isOpen();
    
        /**
         * Attempt to open the door.
         * @return {@code true} if the door was successfully opened, 
         * {@code false} if not.
         * In other words, if a call to {@code open} returns {@code true}, a
         * subsequent call to {@link #isOpen} will return {@code true}.
         */
        boolean open();
    
        /**
         * Attempt to close the door.
         * @return {@code true} if the door was successfully closed, 
         * {@code false} if not.
         * In other words, if a call to {@code close} returns {@code true}, a
         * subsequent call to {@link #isOpen} will return {@code false}.
         */
        void close();
    }
    
    public class RegularDoor implements Door {
        private boolean isOpen;
    
        @Override
        public boolean isOpen() {
            return isOpen;
        }
    
        @Override
        public boolean open() {
            return isOpen = true;
        }
    
        @Override
        public boolean close() {
            return isOpen = false;
        }
    }
    
    
    public class LockedDoor implements Door {
        private Lock lock;
        private boolean isOpen;
    
        @Override
        public boolean isOpen() {
            return isOpen;
        }
    
        @Override
        public boolean open() {
            if (!lock.isLocked()) {
                return isOpen = true;
            }
            return false;
        }
    
        @Override
        public boolean close() {
            return isOpen = false;
        }
    
        // Not shown here - methods to lock and unlock the door
    }