Reputation: 6221
Say I have an Animal class with a Speak() method on it. Normally, Dog and Cat would be subclasses of Animal, and provide their own implementation:
public class Animal {
public abstract void Speak();
}
public class Dog : Animal {
public override void Speak() {
Console.WriteLine("Woof!");
}
}
public class Cat : Animal {
public override void Speak()
Console.WriteLine("Meow!");
}
}
Animal dog = new Dog();
Animal cat = new Cat();
A second way would be to inject the different behaviour:
public class Animal {
private readonly String noise;
public Animal(String noise) {
this.noise = noise;
}
public abstract void Speak() {
Console.WriteLine(noise);
}
}
Animal dog = new Animal("Woof!");
Animal cat = new Animal("Meow!");
The advantage I see with the second way is that it's DRYer, and there are less classes, and it's more flexible.
The disadvantage I see is that the code is less clear, and perhaps more error prone. The variable names must give some clue as to what implementation the class has. Also, if you have a class that needs to be injected with both a cat and a dog, it is up to the developer to correctly make sure that a new Animal("woof!") and and new Animal("meow!") get's passed in in the correct order required by the class, which seems fragile:
public class House {
public House(Animal cat, Animal dog) {
...
}
public void letCatOutside() {
cat.Speak();
}
public void letDogOutside() {
dog.Speak();
}
}
The issue is that I seem to be coding more and more like the second example where I factor out the differences, and then inject them into a common class. I fear that I might be getting into a bad habit. Am I? I hope some more experienced developers can shed some light on the situation.
Upvotes: 1
Views: 136
Reputation: 48240
Rename Animal
, Dog
, Cat
to Foo
, Bar
, Qux
and the answer is anything but obvious.
Classes are meant to capture a common behavior and if a common behavior of Foo
can be identified by a string
then you don't necessarily need a class hierarchy around that string
. What if you need not only to speak but also to move, pet, eat, sleep and do other things? Would you end with a huge hierarchy of ThreeLegSpidersThatBarelyEatButSometimesCanBePetted
? In such case a family of Decorators would help and that makes it more like the second approach.
What I try to say is that there is no simple answer to your questions and it all depends on a broader context. Sometimes you would start with one approach and end up with the other, just because ... (fill up with a reason).
Upvotes: 0
Reputation: 16348
You clearly want the first option because a Dog is a different animal from a Cat and they're doing the same behaviour differently (Speak). Also they have specific behaviour as well.
There are cases when using Animal is enough, it depends on the app requirement, however the number of classes or DRY is NEVER a modeling principle. You'll define as many classes as it make sense for the business. DRY simply means "don't put the same behaviour in more than one place" but it does matter in which layer you're working and if re-using a behaviour will make your code tightly coupled.
I say to worry about how well the business objects are modelling the business concepts and use cases, then about proper encapsulation, then about decoupling. A lot of things comes with experience, though. I would say to leave DRY aside until the code in front of you screams "I'm implementing the exact behaviour elsewhere, please use that"
Oh, and it's not a case of DI here. You're 'injecting' some data used by a behaviour, not a behaviour provider. And House should never have those methods, because a house can't decide things, it's a lifeless object. A Human or a PetOwner on the other hand can decide to let the Animal out.
Upvotes: 1