While working on a project I’ve come up with some design solution. I am having a hard time relating it to any general design pattern or analyzing this situation in details. This is also prohibiting me to find any better design for this.
This is a simple analogy of my solution –
Both Kangaroo
and Tiger
can walk
but only Kangaroo
can jump
and only Tiger
can run
.
Animal<TLeg> where TLegs: ILegs
{
TLegs Legs;
void Walk(){ Legs.Walk(); }
}
Kangaroo: Animal<IJumpingLegs>
{
void Jump() { Legs.Jump(); }
}
Tiger: Animal<IRunningLegs>
{
void Run() { Legs.Run(); }
}
ILegs
{
void Walk();
}
IRunningLegs : ILegs
{
void Run();
}
IJumpingLegs : Ilegs
{
void Jump();
}
[Edit]
Problem Description:
Here, Kangaroo and Tiger share common functions and a property Legs. Therefore, I have a base class for common functions. Base class needs to have access to this property Legs
.
Functions in subclasses too rely on Legs and based on type of Legs, subclass will need to call different functions of this property.
Basically, two concrete animals need some common methods and their uncommon methods(Jump and Run) require different TYPE of a common property.
For this problem, I have come up with the solution described above. Hoping that I am able to describe my problem, I would like to know if there could be any alternate solution.
Does this problem/solution sound familiar? Is there anything that can be improved?
3
This described problem reminds me the Adapter and Facade Patterns: Being Adaptive.
While reading your question, i also have remembered a famous funny old saying:
If it walks like a duck and quacks like a duck, then it must be a turkey wrapped with a duck adapter…
You may get detailed explanation from the following book – Head First Design Patterns
In addition, there are SOLID principles and Liskov Substitution Principle is one of them. It also addresses this issue with a different flavor.
If it looks like a duck, quacks like a duck, but needs batteries – you probably have the wrong abstraction
2
It is a mistake to use inheritance with the Legs
classes and interfaces.(But if your teacher/boss insists that you have to…you’ll probably have to give in.)
Your design works well enough for the requirements you listed. But suppose there is a Horse
that can both run
and jump
. How do you implement that?
I suggest that WalkingLegs
, RunningLegs
, and JumpingLegs
should all be separate classes with no inheritance. The Tiger
would have WalkingLegs
and RunningLegs
, the Kangaroo
would have WalkingLegs
and JumpingLegs
, and the Horse
would have all three.
The above suggestion confuses the Legs
metaphor. What we are really modeling is behavior, not types of legs.
Using classes to represent a behavior is part of the strategy pattern. In the strategy pattern, behaviors have different implementations (in this example, different implements of walking, jumping, etc.)
2
To me, it seems like case of bad abstraction. Base classes are for abstracting behavior that is same for all expected children. Which is wrong in your example. In your case you either want Move
virtual method, and then override it in concrete animals. Or you want Jump
and Run
methods, but then there is nothing common between them, so abstracting it away doesn’t make sense.
And there is one rule I like when doing this :
Abstractions should always be seen from point of who will use those abstractions. Not from the inside of the implementation.
If you are writing some abstraction and you haven’t written code that will use this abstraction, then you are doing something wrong.
1
I would probably implement something along the lines of this:
public abstract class Animal: ILegs
{
public virtual void Walk(){/*Base logic for walking*/}
}
public interface ILegs
{
void Walk();
}
public interface IJumpingLegs
{
void Jump();
}
public interface IRunningLegs
{
void Run();
}
public class Kangeroo : Animal, IJumpingLegs
{
public void Jump()
{
//Logic for Jumping
}
}
public class Tiger : Animal, IRunningLegs
{
public void Run()
{
//Logic for Jumping
}
}
//Custom Implementation
public class OtherAnimal : Animal
{
public override void Walk() { /*Custom how I walk*/ }
}
When instanciating my objects I would use
OtherAnimal a = new OtherAnimal();
a.Walk(); //Custom override logic fro OtherAnimal class
Tiger t = new Tiger();
t.Run(); //Custom Run logic
t.Walk(); //Logic from Animal abstract class
Each Tiger/Kangeroo class only implements the necessary interface for each class and is not inheriting any unnecessary logic and not breaking the Liskov Substitution Principle mentioned by @Yusubov.
The Tiger/Kangeroo class also use the base Walk() logic from the Animal abstract class and have the option if necessary to override it lie the OtherAnimal class does.
Just don’t implement a Fish class or similar extending the Animal. Fish don’t walk, or have Legs!!
4