Reputation: 37
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
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 stackpop
: removes and element from the top of the stack. error if the stack is emptytopElement
: return the top element in the stack. error if the stack is emptyisEmpty
: return true is the stack is empty, false otherwiseAt 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
: PassTestSetBasedStackPush
: 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
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