Mark Evans
Mark Evans

Reputation: 37

How to avoid casting derived types - Liskov's Principle violation

I want to avoid using casting base class type to derived class type I can do this successfully If I want to access generic functionality but if I want specialised functionality I cant access this without casting

I have written code to demonstrate what I've already tried.

public abstract class Animal : IAnimal
{
    public void Move()
    {        
    }
}

public interface IAnimal
{
     void Move();
}

public interface IDog:IAnimal
{
    void bark();
}

public class Dog : IDog
{
    public void Move()
    {

    }

    public void bark()
    {

    }
}

static void Main(string[] args)
{
    Animal animal = null;
    IDog dog = animal as IDog;

    dog.bark(); // can access specialized method

    IAnimal puppy = new Dog();
    puppy.Move(); // can only access generic functions    
}

How can I re-design classes to access "bark" method without casting?

Upvotes: 0

Views: 1021

Answers (2)

expandable
expandable

Reputation: 2300

Let's takle the Liskov Substitution Principle first and then talk about OOP and inheritance.

First, lets talk about Abstract Data Types. In her paper she uses the concept of objects from types.

An Abstact Data Type (ADT) is a description of a type with all it's operations and behaviors. All clients of an ADT should know what to expect when using it.

Here's an example:

Let's define a Stack as an ADT

Operations: push, pop, topElement, size, isEmpty

Behaviors:

  • push : always adds an element to the top of the stack!
  • size : return the number of elements in the stack
  • pop : removes and element from the top of the stack. error if the stack is empty
  • topElement : return the top element in the stack. error if the stack is empty
  • isEmpty : return true is the stack is empty, false otherwise

At this point we desribed what is a Stack in terms of it's operations and how it should behave. We are not talking about clases here nor concrete implementations. This makes is an Abstract Data Type.

Now lets make a type hierarchy. In C# both interfaces and classes are types. They are different as interfaces define only operations, so in a sense they are a contract. They define the operations of an ADT. Usually people do assume that only classes that inherit from one another define a type hierarchy. It's true that classes that inherit from one another are called Superclass or Baseclass and a Subclass, but from the point of view of Types we do have Supertype and Subtype for both interfaces and classes as they both define types.

NOTE: For simplicity i'll skip error checking in the implementations of the methods

// interfaces are types. they define a contract so we can say that
// they define the operations of an ADT
public interface IStack<T> {
    T Top();
    int Size();
    void Push(T element);
    void Pop();
    bool IsEmpty();
    }

// the correct term here for C# whould be 'implements interface' but from 
// point of view of ADTs and *Types* ListBasedStack is a *Subtype*
public class ListBasedStack<T> : IStack<T> {

    private List<T> mElements;

    public int Size() { return mElements.Count; }

    public T Top() { mElements(mElements.Count - 1); }

    public void Push(T element) { mElements.Add(element); }

    public void Pop() { mElements.Remove(mElements.Count - 1); }

    public bool IsEmpty() { return mElements.Count > 0; }
}

public class SetBasedStack<T> : IStack<T> {

    private Set<T> mElements;

    public int Size() { return mElements.Count; }

    public T Top() { mElements.Last(); }

    public void Push(T element) { mElements.Add(element); }

    public void Pop() { mElements.RemoveLast(); }

    public bool IsEmpty() { return mElements.Count > 0; }
}

Notice that we have two Subtypes of the same ADT. Now lets consider a test case.

public class Tests { 

    public void TestListBasedStackPush() {
        EnsureUniqueElementsArePushesToAStack(new ListBasedStack<int>());
    }

    public void TestSetBasedStackPush() {
        EnsureUniqueElementsArePushesToAStack(new SetBasedStack<int>());
    }

    public void EnsureUniqueElementsArePushesToAStack(IStack<int> stack) {

          stack.Push(1);
          stack.Push(1);

          Assert.IsTrue(stack.Size() == 2);
   }
}

And the results are:

  • TestListBasedStackPush: Pass
  • TestSetBasedStackPush: FAIL!

SetBasedStack violates the rules for push: always adds an element to the top of the stack! as a set can contain only unique elements and the second stack.Push(1) wont add new element to the stack.

This is a violation of LSP.

Now about examples and type hierarchies like IAnimal and Dog. When you are in the right abstaction level a type should behave like it's suposed to. If you do need a Dog, use a Dog. If you do need an IAnimal, use IAnimal.

How do you access Bark if you have IAnimal? You DON'T!!. You are at the wrong level of abstraction. If you do need a Dog, use a Dog. Cast if you have to.

public class Veterenerian {
    public void ClipDogNails(IAnimal animal) { } // NO!
    public void ClipDogNails(Dog dog) { } // YES!
}

private Veterenerian mOnDutyVeterenerian;
private List<IAnimal> mAnimals;

public ClipAllDogsNails() {
     // Yes
     foreach(var dog in mAnimals.OffType<Dog>()) {
         mOnDutyVeterenerian.ClipDogNails(dog);
     }
     // NO
     foreach(var animal in mAnimals) {
         mOnDutyVeterenerian.ClipDogNails(animal);
     }
}

Do you need to cast? Sometimes yes. If it better to not do it? Yes, most of the time.

How do you solve the above problem? You can make the Dog clip it's own nails. Are you doing to add method ClipNails to IAnimal and make only animals with nails implement this and leave other animal subclasses leave this method empty? NO! Because it doesn't make sense in the level of abstraction of IAnimal and it also violates LSP. Also if you do this you can call animal.ClipNails() and this will be fine, but if you do have a schedule that says that dogs should clip nails on Friday other animals Monday your stuck again as you can make all animals clip their nails, not only dogs.

Sometimes an object of one Type is to be used by objects from another Type. Some operations doesn't make sense in a type. This example illustates how a Dog cannot clip it's nails. It should be done by a Veterenerial.

Yet we do need to work on the IAnimal level of abstraction. All things in a Veterenerian Clinic are animals. But sometimes some operations need to be performed on specific type of animal, a Dog in this case, so we do need to filter the animals by their Type.

But that's a completely different problem from the above example with Stack.

Here's an example on when casting should not be used and the client code should not case about the concrete implementation:

public abstract class Serializer {
    public abstract byte[] Serialize(object o);
}

public class JSONSerializer : Serializer {
    public override byte[] Serialize(object o) { ... }
}

public class BinarySerializer : Serializer {
    public override byte[] Serialize(object o) { ... }
}

public void DoSomeSerialization(Serializer serializer, Event e) {
    EventStore.Store(serializer.Serialize(e));
}

DoSomeSerialization method should not care about the serializer that is passed to it. You can pass any Serializer that adheres to the Serializer spec, it should work. That's the point of having an abstraction with multiple implemenations. DoSomeSerialization works on the level of Serializer. We can define the Serializer as an ADT. All classes that are derive from Serializer should adhere to the specification of the ADT and the system works just fine. No casting here, no need to do casting here as the problem is different.

Upvotes: 0

Kacper Turowski
Kacper Turowski

Reputation: 71

Short answer: You cannot and you shouldn't be able.

What you could do instead, is probably implement a MakeNoise() method in IAnimal interface because you'd expect animals in general to make noises.

However, if you insist on keeping Bark() on IDog, you wouldn't expect an IDuck to be able to access it - it should have a Quack() method. Neither will be available from objects downcasted to IAnimal because how can you guess whether it's a Duck or a Dog?


I'll post bit more "real life" example of why you might need inheritance in programming, because example you've provided is sort-of "book example" and thus it is obscure and vague as well.

using System.Collections.Generic;

namespace ConsoleApp1
{
    public static class DocumentHandling
    {
        public static List<IAccountable> Documents;
        public static dynamic InternalService { get; set; }
        public static dynamic IRS { get; set; }

        public static void HandleDocuments()
        {
            foreach (var document in Documents)
            {
                document.Account();
            }
        }
    }

    public interface IAccountable
    {
        void Account();
    }

    public abstract class Document
    {
        public int DatabaseId { get; set; }
        public string Title { get; set; }

    }

    public abstract class DocumentWithPositions : Document
    {
        public int[] PositionsIds { get; set; }
    }

    public class Invoice : DocumentWithPositions, IAccountable
    {
        public void Account()
        {
            var positions = DocumentHandling.InternalService.PreparePositions(this.PositionsIds);
            DocumentHandling.IRS.RegisterInvoice(positions);
        }
    }

    public class Receipt : DocumentWithPositions, IAccountable
    {
        public void Account()
        {
            Invoice invoice = DocumentHandling.InternalService.ConvertToReceipt(this);
            invoice.Account();
        }
    }
}

See how I can stuff both Invoice and Receipt documents in single List (because they're downcasted to IAccountable)? Now I can account them all at once, even though their concrete implementations handle accounting process differently.

Upvotes: 6

Related Questions