How do you keep up with 'requiring new methods in an interface' (Following LSP and adding new methods to interface seems to violate ISP)

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/289813

Question

  • I have a game that deals with opening and closing doors and the Door Engine
    deals with IDoor interface which has Open() and Close() contracts
  • So far so good. the game is tested and works fine.
  • Now a new feature is required, doors can be automatically closed with a timer, i.e a new contract- SetTimer() is needed. Only parts of my Door Engine need to deal with setting the timer, the rest
    of code need not deal with setting timer, i.e the old IDoor interface is enough.

How would you do it?

  1. Add the SetTimer() to IDoor interface? and implement the method in the derived classes approp'ly, the existing Door classes can have a dummy method and our special TimedDoor can have approp implemention. Moreover, you could have a 'DoesSupportTimedClosing' contract to make it perfect LSP compliant.
  2. Respect ISP (I know that ISP is for letting clients create their implementations without having to implement the entire interface, but can we consider my internal code as a client too, or am I having a wrong understanding) and create a new interface - ITimedDoor : IDoor (ITimedDoor will have the SetTimer contract) you can create timeddoors that implement ITimedDoor, but, is this violation of LSP? If I want some arbitrary piece of existing code to set a timer on a door - I would need to do type checks and type casts to call the ITimedDoor.SetTimer() on the IDoor object it has.

In a nut shell, if you have an Interface - IInterface1 and existing code thats already tested and works fine, how would you handle the requirement of adding new methods to it?

Some references on related topics:

Was it helpful?

Solution

My understanding of the Liskov Substitution Principle differs from yours, i.e. any implementation of IDoor should behave as a door – do what one expects when Open() or Close() is called – this includes objects implementing ITimedDoor (with ITimedDoor a subclass of IDoor). It does not imply that any arbitrary piece of code should be able to call SetTimer() on any IDoor with most doors not doing anything useful on a call to SetTimer().

The Interface Segregation Principle – i.e. no client should depend on methods it does not use – combines very well with my understanding of the Liskov Substitution Principle:

  1. Any interface should be minimal: code code should depend on methods it does not use.
  2. Every class that implements an interface should adhere to the same semantics and every object of a class that implements the interface should be exchangeable with every other object of any other class that implements the interface: classes should not implement methods that behave differently (including no-op) than other implementations of that interface's method.

In general, having a collection of IInterface1-objects and then execute a different action depending on the type (or sub interface) will be unavoidable (or at least difficult to avoid). Using the visitor pattern can be an alternative to run time type checking.

However, in this specific situation – of a game with doors some of which are timed doors – you might be better of with a level builder which build a level containing doors, some of them timed, and sets the timers of the timed doors (or connects the timers to some other events in the level).

Licensed under: CC-BY-SA with attribution
scroll top