1

In the example below, we have two instances of the Terrier class, which derives from Dog.
One instance is referenced by a variable of type Terrier.
Using this variable, you have access to all members of the Terrier class.
On the other hand, the variable whose type is Dog can only reference members of the Dog class, even though the reference points to an instance of a Terrier.

Terrier bubba = new Terrier("Bubba", 2, "Happy");
bubba.Growl();    // Can call Terrier.Growl

Dog jack = new Terrier("Jack", 17, "Surly");
jack.Growl();     // ERROR: Can't call Growl method

I need to implement a class MyPets which has a List<Pets> which can hold either of Cat Object or a Dog Object.
Both these object have some common methods like MakeNoise() but some unique methods too which are not in base class like Cat has method ClimbTree().
This MyPets class will also have a method that will iterate through List<animals> and calls the MakeNoise() method and ClimbTree() method.

What should be the best approach to achieve this, using Abstract base class or some other method?

Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
narane13
  • 11
  • 2
  • I suspect, `Pets` and `animals` means the same class? You should explain what is the semantic context of `MyPets` calling `MakeNoise()` and `ClimbTree()`. If it is, that any `Pet` has one key ability, you can abstract it into your base class. There are other approaches if your pet ability model is different. – grek40 May 25 '16 at 06:30
  • Use an Abstract base class of `Pet` (Or an interface) to make it so both `Dog` and `Cat` inherit from it with the method `makeNoise()`. Then, during your iteration you can call `makeNoise()` and check if they are a subclass of `Cat` and if so, call the method `climbTree()`. [See here for dealing with casting the object](http://stackoverflow.com/questions/12234097/how-to-cast-object-to-its-actual-type) and [see here for checking the type](http://stackoverflow.com/questions/2742276/how-do-i-check-if-a-type-is-a-subtype-or-the-type-of-an-object) – Draken May 25 '16 at 06:37

3 Answers3

1

With regards to my comment, something along these lines should solve your issue:

public class Visitor
{
    public void doItterate(Cat c)
    {
        Console.WriteLine(c.ToString());
        c.makeNoise();
        c.climbTree();
    }

    public void doItterate(Dog d)
    {
        Console.WriteLine(d.ToString());
        d.makeNoise();
    }
}

public abstract class Pet
{
    public Pet(string name, int age, Mood mood)
    {
        this.MoodOfPet = mood;
        this.Name = name;
        this.Age = age;
    }

    public string Name
    {
        get;
        private set;
    }

    public int Age
    {
        get;
        private set;
    }

    public Mood MoodOfPet
    {
        get;
        private set;
    }

    public abstract void makeNoise();
    public override string ToString()
    {
        return this.Name + " is " + this.Age +
            " years old  and feels " + this.MoodOfPet;
    }

    public abstract void accept(Visitor v);
}

public enum Mood
{
    Surly,
    Happy
}

public abstract class Dog : Pet
{
    public Dog(string name, int age, Mood mood): base (name, age, mood)
    {
    }

    public override void makeNoise()
    {
        Console.WriteLine(this.Name + " is woofing");
    }

    public override void accept(Visitor v)
    {
        v.doItterate(this);
    }
}

public class SheepDog : Dog
{
    public SheepDog(string name, int age, Mood mood): base (name, age, mood)
    {
    }
}

public class Cat : Pet
{
    public Cat(string name, int age, Mood mood): base (name, age, mood)
    {
    }

    public void climbTree()
    {
        Console.WriteLine(this.Name + " is climbing");
    }

    public override void makeNoise()
    {
        Console.WriteLine(this.Name + " is meowing");
    }

    public override void accept(Visitor v)
    {
        v.doItterate(this);
    }
}

public class Terrier : Dog
{
    public Terrier(string name, int age, Mood mood): base (name, age, mood)
    {
    }

    public void growl()
    {
        Console.WriteLine(this.Name + " is growling");
    }

    public override void makeNoise()
    {
        growl();
    }
}

public class MyPets
{
    private Visitor visitor = new Visitor();
    public MyPets()
    {
        Pets = new List<Pet>();
    }

    public List<Pet> Pets
    {
        get;
        private set;
    }

    public void addPet(Pet p)
    {
        Pets.Add(p);
    }

    public void itterate()
    {
        foreach (Pet p in Pets)
        {
            p.accept(visitor);
        }
    }
}

Fiddle

It's standard OOP design using abstract methods that get overloaded in more concrete classes later on.

EDIT Now it's using the visitor pattern

Running the following code:

MyPets pets = new MyPets();
pets.addPet(new Cat("Bob", 2, Mood.Surly));
pets.addPet(new Terrier("Jack", 17, Mood.Surly));
pets.addPet(new SheepDog("Bubba", 2, Mood.Happy));
pets.itterate();

Produces these results:

Bob is 2 years old and feels Surly

Bob is meowing

Bob is climbing

Jack is 17 years old and feels Surly

Jack is growling

Bubba is 2 years old and feels Happy

Bubba is woofing

Community
  • 1
  • 1
Draken
  • 3,134
  • 13
  • 34
  • 54
  • The `itterate` part can become quite ugly if more animals with special abilities join the party. – grek40 May 25 '16 at 07:49
  • Yep, very true. However that is something the OP wanted it to do, if you feel you have a better solution, I'm (And I'd assume the OP) am open to suggestions – Draken May 25 '16 at 08:03
  • 1
    Gonna wait for OP clarification before I start to throw visitor patterns, ability abstraction or annotation/reflection into the pool. Your answer is fine as lone as the OP doesn't state more specific requirements. – grek40 May 25 '16 at 08:24
  • Visitor pattern would be quite useful, alas I've never been a fan of it unless I have to use it – Draken May 25 '16 at 08:27
  • [Then you are better of using the visitor pattern as @grek40 has stated](http://www.dofactory.com/net/visitor-design-pattern) – Draken May 25 '16 at 08:34
  • For now I am happy with this solution as it perfectly solves my issue. but as grek40 says this becomes ugly when more animals with special abilities join the party. But still wanted to know the other strategy/pattern to solve these kind of problems... – narane13 May 25 '16 at 08:35
  • I've updated it to use a variation of the visitor pattern, I have also updated the fiddle. There are possibly still better ways, @grek40 might be able to improve upon it – Draken May 25 '16 at 09:00
  • Thanks for the quick reply and though the first solution as suggested by @Draken solves my problem I am happy with both solutions... – narane13 May 25 '16 at 09:15
0

In this answer, I present an attribute based solution. In some aspects it might be easier to maintain than the solution proposed by @Draken, but in other aspects it is less useful.

The idea: annotate pet method with an attribute, in order to mark it as an ability. I kept the attribute very simple, its only a marker without any additional meta information.

The IteratePetAbilities() method is the key method to access the pet abilities. It uses reflection in order to find methods that are marked with the ability attribute and invokes them.

// Note: Target method is required to have an empty parameter list
[AttributeUsage(AttributeTargets.Method)]
public sealed class PetAbilityAttribute : Attribute
{
}


public class MyPets
{
    public MyPets()
    {
        Pets = new List<Pet>();
    }

    public ICollection<Pet> Pets { get; set; }

    // Discover PetAbilityAttribute methods on the concrete pet type and invoke them dynamically
    public void IteratePetAbilities()
    {
        foreach (var pet in Pets)
        {
            Console.WriteLine("Pet '" + pet.PetName + "' enters the stage");
            var abilities = pet.GetType().GetMethods().Where(x => x.GetCustomAttributes(typeof(PetAbilityAttribute), true).Any());
            foreach (var abilityMethod in abilities)
            {
                Console.Write("# {0,12}() # ", abilityMethod.Name);
                abilityMethod.Invoke(pet, new object[] { });
            }
            Console.WriteLine();
        }
    }
}


public abstract class Pet
{
    public string PetName { get; set; }

    [PetAbility]
    public abstract void MakeNoise();

    // Note: this is not marked as an ability here
    public abstract void GoSleep();
}


public class Dog : Pet
{
    [PetAbility] // no effect, since base already has this attribute
    public override void MakeNoise()
    {
        Console.WriteLine("Says woof");
    }

    // not marked as an ability
    public override void GoSleep()
    {
        Console.WriteLine("Goes to the dogs house and sleeps");
    }
}


public class Terrier : Dog
{
    [PetAbility]
    public void HuntACat()
    {
        Console.WriteLine("Starts running after a poor little cat");
    }


    [PetAbility] // Unlike a regular dog, the Terrier goes to sleep by ability :)
    public override void GoSleep()
    {
        base.GoSleep();
    }
}


public class Cat : Pet
{
    public override void MakeNoise()
    {
        Console.WriteLine("Says meow");
    }

    [PetAbility]
    public void ClimbTree()
    {
        Console.WriteLine("Climbs a tree and is to scared to return on its own");
    }

    [PetAbility] // makes GoSleep an ability only for cats, even though the method exists in base class
    public override void GoSleep()
    {
        Console.WriteLine("Refuses to sleep and starts sharpening its claws");
    }
}



class Program
{
    static void Main(string[] args)
    {
        var myPets = new MyPets();
        myPets.Pets.Add(new Cat() { PetName = "Super Cat" });
        myPets.Pets.Add(new Dog() { PetName = "Little Dog" });
        myPets.Pets.Add(new Terrier() { PetName = "Hunter" });

        myPets.IteratePetAbilities();
    }

}

Output

Pet 'Super Cat' enters the stage
#    MakeNoise() # Says meow
#    ClimbTree() # Climbs a tree and is to scared to return on its own
#      GoSleep() # Refuses to sleep and starts sharpening its claws

Pet 'Little Dog' enters the stage
#    MakeNoise() # Says woof

Pet 'Hunter' enters the stage
#     HuntACat() # Starts running after a poor little cat
#      GoSleep() # Goes to the dogs house and sleeps
#    MakeNoise() # Says woof

Pro

  • It is easy to add new abilities in any Pet subclass, no matter how deep it is nested in the subclass hierarchy (single point of code change)

Con

  • Uses reflection
    • Becomes more complex when validity must be enforced (For example, what if the attribute is applied to a method with input parameter?).
    • Some people say it's slower than compile time binding (I didn't measure myself, but I tend to believe them)
  • Limited control (For example, if you want to output the pet abilities in a certain order, additional work is required)
    • This generally applies to any inter-dependency between multiple abilities

Edit

Side note:

If x.GetCustomAttributes(typeof(PetAbilityAttribute), false) (instead of true) is called in IteratePetAbilities(), then Cat.MakeNoise would not be discovered, because the override method is not marked with the attribute.

grek40
  • 13,113
  • 1
  • 24
  • 50
0

You can simply use is and/or as operators if the number of subclasses is relatively small.

List<Pet> pets = new List<Pet>
{
    new Cat("cat1", 1, "Happy"),
    new Cat("cat2", 2, "Happy"),
    new Dog("dog", 1, "Surly"),
    new Terrier("dog", 3, "Happy")
};

// in your Iterate() method
foreach (var pet in pets)
{
    // common methods here
    pet.MakeNoise();

    if (pet is Cat)
    {
        var animal = (Cat)pet;
        animal.ClimbTree();
    }
    else if (pet is Terrier)
    {
        var animal = (Terrier)pet;
        animal.Growl();
    }
}

Visitor pattern is worth using when data should be separated from operations which is not your case.

Attribute solution should be avoided because of reflection.

Alex G.
  • 909
  • 1
  • 9
  • 16
  • Actually, data are separated from operations, since the `Pet` class hierarchy contains the data (I'd consider abilities as data too) and the operation of accessing the abilities belongs to `MyPets`, which is not part of the data hierarchy. However, I agree that an easy if-else solution also belongs here. Unfortunately, @Draken edited this out of his answer instead of adding the visitor pattern. – grek40 May 31 '16 at 05:25