Reputation: 898
I've spent hours trying to find a way to implement this and so far I haven't found a good solution, so I'm hoping someone could please help point me in the right direction.
I currently have a C# Winforms project that has an abstract base class with several child classes. Most of the methods are the same but each child class has a few additional methods specific to it.
I want to be able to declare a type of the class being used once and pass that type to all my other methods without having to manually check the type everywhere by going "If Class Type=A Do This Else If Class Type=B Do That, and so on".
The problem is that I need to pass the base class or interface to accomplish this. However, by doing so it means I can no longer access the properties or methods specific to the child classes and I don't know how to fix/workaround this.
Here's a simplified example of what I'm trying to do:
Interface and Class Structure
public interface IAnimal
{
string NameOfAnimal { get; set; }
void Eat();
}
public abstract class Animal : IAnimal
{
public abstract string NameOfAnimal { get; set; }
public abstract void Eat();
}
public class Bird : Animal
{
public Bird()
{
NameOfAnimal = "Bob the Bird";
NumberOfFeathers = 100;
}
// Interface members
public override string NameOfAnimal { get; set; }
public override void Eat()
{
System.Windows.Forms.MessageBox.Show("Eating like a Bird");
}
// Bird specific properties and methods
public int NumberOfFeathers { get; protected set; }
public string SomeOtherBirdSpecificProperty { get; protected set; }
public void Fly()
{
// Fly like a bird
}
}
public class Fish : Animal
{
public Fish()
{
NameOfAnimal = "Jill the Fish";
DoesFishHaveSharpTeeth = true;
}
// Interface members
public override string NameOfAnimal { get; set; }
public override void Eat()
{
System.Windows.Forms.MessageBox.Show("Eating like a Fish");
}
// Fish specific properties
public bool DoesFishHaveSharpTeeth { get; protected set; }
public string SomeOtherFishSpecificProperty { get; protected set; }
}
Main Form Code
private void button1_Click(object sender, EventArgs e)
{
IAnimal myCustomAnimal = null;
string animalTheUserSelected = "Bird";
// Only want to manually specify this once
switch (animalTheUserSelected)
{
case "Bird":
myCustomAnimal = new Bird();
break;
case "Fish":
myCustomAnimal = new Fish();
break;
default:
break;
}
DoSomethingWithACustomAnimal(myCustomAnimal);
}
private void DoSomethingWithACustomAnimal(IAnimal myAnimal)
{
// This works fine
MessageBox.Show(myAnimal.NameOfAnimal);
myAnimal.Eat();
// This doesn't work
MessageBox.Show(myAnimal.NumberOfFeathers);
myAnimal.Fly();
}
I understand why I'm having that issue in the main form code... the compiler doesn't know which type of animal is being passed to it yet so it doesn't know what to show. However, I don't know what I should do to fix this.
I've tried:
Putting all the animal-specific properties in the interface. This works but violates several OOP principles. A fish doesn't have feathers, etc. so these specific properties don't belong there.
Manually checking the type everywhere by going something like "If Type=Fish do abc Else If Type=Bird do def". This also works but violates the DRY principle because I'm repeating myself everywhere. Also with a lot of methods using animals, this will be a nightmare to maintain in the future.
Explicitly casting IAnimal to a specific animal like ((Bird)myCustomAnimal).NumberOfFeathers. This also works but I don't know what cast to use at compile-time. This won't be known until the user selects an animal at run-time.
So I'm just wondering how I can solve this issue?
More specifically, I'm wondering how I can re-design the above code so that I can both:
A) Explicitly declare a type of animal only once and pass that type everywhere (without having to do lots of manual checks in every method to see what type it is before doing something with it)
and also
B) Somehow still have a way to manually access the animal-specific properties like someBird.NumberOfFeathers when I need to.
Any ideas?
Upvotes: 1
Views: 914
Reputation: 13177
One solution would be to use the visitor pattern to define the operations you want to perform on your animal instances.
First you would define a visitor interface which provides a method for each type of animal you have in your hierarchy.
public interface IAnimalVisitor
{
void VisitBird(Bird bird);
void VisitFish(Fish fish);
}
And then you would need to modify your animal classes and interfaces to include a method which accepts a visitor, like so:
public interface IAnimal
{
string NameOfAnimal { get; set; }
void Accept(IAnimalVisitor visitor);
}
Your actual animal classes now look something like this:
public class Bird : IAnimal
{
public Bird()
{
NameOfAnimal = "Bob the Bird";
NumberOfFeathers = 100;
}
public string NameOfAnimal { get; set; }
public int NumberOfFeathers { get; protected set; }
public void Accept (IAnimalVisitor visitor)
{
visitor.VisitBird(this);
}
}
public class Fish : IAnimal
{
public Fish()
{
NameOfAnimal = "Jill the Fish";
DoesFishHaveSharpTeeth = true;
}
public string NameOfAnimal { get; set; }
public bool DoesFishHaveSharpTeeth { get; protected set; }
public void Accept (IAnimalVisitor visitor)
{
visitor.VisitFish(this);
}
}
Now for anything you want to do with each of your animals you will need to define an implementation of the IAnimalVisitor
interface. In your example you displayed message boxes that showed information about the animal so an implementation of the IAnimalVisitor
interface that does that could look like this:
public class AnimalMessageBoxes : IAnimalVisitor
{
private void VisitAnimal(IAnimal animal)
{
MessageBox.Show(animal.NameOfAnimal);
}
public void VisitBird(Bird bird)
{
visitAnimal(bird);
MessageBox.Show(bird.NumberOfFeathers);
}
public void VisitFish(Fish fish)
{
visitAnimal(fish);
MessageBox.Show(fish.DoesFishHaveSharpTeeth);
}
}
Now you just need to pass your visitor to your animal and the correct information will be displayed. Your event handling code now looks something like this:
string animalTheUserSelected = "Bird";
IAnimal myCustomAnimal = null;
switch (animalTheUserSelected)
{
case "Bird":
myCustomAnimal = new Bird();
break;
case "Fish":
myCustomAnimal = new Fish();
break;
default:
break;
}
AnimalMessageBoxes msgBoxes = new AnimalMessageBoxes();
myCustomAnimal.Accept(msgBoxes);
If you want to do something else to your animal instances (save them to a file, generate a UI, play sounds...) you just need to define a new IAnimalVisitor
implementation that provides your desired behaviour.
For the sake of balance I will say that this might not be an appropriate design as it adds some additional complexity; each 'operation' requires you to implement a visitor and the addition of another animal to your hierarchy requires you to update your visitor interface and all of it's implementations to account for the new case.
Depending on your point of view this can be either good or bad. Some consider the points above to be bad and a reason to avoid the visitor pattern and to use the other methods already suggested. Others (like me) consider the points above to be a good thing; your code will now only compile when you provide an implementation for each animal in your hierarchy and your operations are separated into small, dedicated classes.
My suggestion would be to try the SSCCE I have provided and research the visitor pattern further to decide if this solution is acceptable for your requirements.
Upvotes: 3
Reputation: 8581
I understand, what you are trying to achive, but your approach is just wrong.
Think it this way: does your "Main" class/form realy need to know if there is a bird or a dog? The answer is "NO". There are some common properties which you exposed via an interface (i suggest using a base class here!). Everything else is specific to the given animal. The easiest approach is extending your interface with a DoAnimalSpecificStuff() method - which would perform the specific opperations.
When it comes to presentation, you should take a look at the MVP and MVVM Patterns.
P.S. use a Factory Pattern for animal creation!
Upvotes: 2
Reputation: 45135
It really depends on exactly what you are trying to do and what you are hoping to achieve. All the things you've tried so far are possible approaches, but without more context it's hard to give a general answer. It depends on what the right level of abstraction is for what you are trying to do.
One thing to remember is that you can have a class implement as many interfaces as you need. So you could do something like:
public interface IAnimal
{
string NameOfAnimal { get; set; }
void Eat();
}
public interface IFly
{
void Fly();
}
public interface IHaveFeathers
{
int NumberOfFeathers { get; set; }
}
Then you Bird
class can be:
public Bird : Animal, IFly, IHaveFeathers
{
// implementation
}
And now in a method you can do something like:
private void DoSomethingWithACustomAnimal(IAnimal myAnimal)
{
// This works fine
MessageBox.Show(myAnimal.NameOfAnimal);
myAnimal.Eat();
var feathered = myAnimal as IHaveFeathers;
if (feathered != null)
{
MessageBox.Show(feathered.NumberOfFeathers);
}
var flier = myAnimal as IFly;
if (flier != null)
{
flier.Fly();
}
}
The other thing to think about is how to abstract what you need to a higher level. So you need to Fly
, but why? What happens with the Animal
that can't fly? By Fly
are you really just trying to Move
? Then perhaps you could do:
public interface IAnimal
{
string NameOfAnimal { get; set; }
void Eat();
void Move();
}
And in your Bird
you can do this:
public Bird : Animal, IFly, IHaveFeathers
{
public override void Move()
{
Fly();
}
public void Fly()
{
// your flying implementation
}
// rest of the implementation...
}
Upvotes: 4
Reputation: 2620
You have to explicitly cast the IAnimal
object to specific type, if you want to access specific properties/methods.
You can use as
operator and then check if the cast was successful like:
Bird b = myAnimal as Bird;
if(b != null)
{
MessageBox.Show(b.NumberOfFeathers);
}
Upvotes: 1