3
  • 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:

  • I think you may be misapplying the lesson in your third link. The problem shown there is that the ProjectTask class effectively breaks the expected behavior of the task class. You could potentially run into all kinds of problems if you supplied a ProjectTask to a method that had a Task parameter. – Daniel Jul 15 '15 at 20:06
  • I would strongly suggest you read the series of blog post wizard and warriors from Eric Lippert. He suggest a lot of good solutions to the kind of problems you are encountering (it sure was eye opener for me) – Laurent Bourgault-Roy Jul 15 '15 at 20:56
  • i think i misunderstood few things. ITimedDoor:IDoor sounds fine and it doesnt violate LSP, ITimedDoor is perfectactly substitutable for IDoor anywhere, its just that it additionally has the SetTimer feature and i think its okay to do some type checks and type casts at approp places, i.e refactor your code to seperate things that deal with acessing the timer and make them accept ITimedDoor parameter and whenever this code needs to be accessed, you can type check and type cast your IDoor to ITimedDoor and pass the ITimedDoor object – user136627 Jul 16 '15 at 10:14
  • @Daniel, the ProjectTask example deals with unexpected behaviour on an already existing base method in derived class, but if i add SetTimer to IDoor, i would still end up with existing Door classes violating LSP as they dont do anything for SetTimer(). the same applies to ProjectTask, if i want to add a new Restart method to Task class, then all the existing Task classes end up violating LSP unless i also add a - CanRestart() to Task – user136627 Jul 16 '15 at 10:21
  • @LaurentBourgault-Roy, thank you, I'll check them. – user136627 Jul 16 '15 at 10:21
4

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).

  • yup, i understood what i misunderstood. i'll look into the visitor and builder pattern pointers. – user136627 Jul 16 '15 at 14:07

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.