Tuesday, March 12, 2013

polymorphism and abstract methods

The abstract methods are in fact a lot like the method signature definitions in an interface. They do not return anything in and of themselves and they require the inheriting subtypes to implement a comparable method signature. You have to address their neediness from a child with the override keyword as you would for a virtual method, but where a virtual method does not require overriding, an abstract method cannot be left without an overriding implementation in all subtypes inheriting from the abstract parent class containing the abstract method. Only abstract classes may have abstract methods. Clearly you may also put regular methods into abstract classes and thus the abstract classes vary from interfaces with this capacity for complication/complexity. Abstract classes are good candidates for middle rungs in an inheritance ladder in which an interface is a grandparent and a regular class is a grandchild in that they will allow for managing logic common to all grandchildren for which they are a parent. I saw John Teague speak at the Austin .NET User Group last night. Of all of the presentations I have seen at ADNUG, this one was probably the very best. It was basically on polymorphism, but went off on some other pretty interesting tangents too, such as abstract classes for example, and it was clarified that encapsulation is not merely the use of private methods, it is of trying to hide internal state, contrary to my prior understanding. Yes, that means private method implementations a lot of the time too, but thinking only in terms of the implementation is shortsighted. (An object's external state should be inaccessible to outsiders.) Prior to this evening, I understood polymorphism to be of situations in which two or more methods had the same name but different signatures where John instead offered: "Polymorphism lets you define different behavior for subtypes, while keeping a consistent contract." The shape this takes is often one in which two method signatures of the same name consume two different subtypes of a common parent, but, as with the poor interpretation of encapsulation, a focus on the implementation can blind one to the underlying goal of polymorphism. In the example below, one method signature takes different subtypes which are upcast to a parent. This too is polymorphism. The subtypes drive the shape-changing. John offered some code not unlike the code he also mentioned at Jimmy Bogard's blog on enumeration classes and not unlike the code I imagine Chris Missal writing when he tweets: "I can't remember the last time I saw a good excuse to use a 'switch' in C#. Ever since LINQ, these have been absent in my code." I imagine Chris' code to have where T contracts and list filtering based on type like this:

public IList<T> GetPersons<T>() where T : Person
{
   return persons.OfType<T>().ToList();
}

 
 

The method above could be used to go fishing in the collection called persons of Person exclusively for a subtype called Teacher for example. This example and the ones given at Jimmy's blog posting and by John last night show ways to refactor away switch statements which may be painful to consistently revisit for revisions. Consider:

Bad

Better

namespace App.Stuff
{
   public enum Taste
   {
      Sweet,
      Sour,
      Salty,
      Bitter
   }
}

 

namespace App.Stuff
{
   public class Food
   {
      public Taste Variety { get; set; }
   }
}

namespace App.Stuff
{
   public interface Food
   {
      int ToothDecay();
   }
}

namespace App.Stuff
{
   public class Gar
   {
      public int Teeth { get; set; }
      public Gar() { Teeth = 100; }
      public void Eat(Food food)
      {
         switch (food.Variety)
         {
            case Taste.Sweet:
               Teeth = Teeth - 10;
               break;
            case Taste.Sour:
               Teeth = Teeth - 2;
               break;
            case Taste.Bitter:
               Teeth = Teeth - 1;
               break;
         }
      }
   }
}

namespace App.Stuff
{
   public class Gar
   {
      public int Teeth { get; set; }
      public Gar() { Teeth = 100; }
      public void Eat(Food food)
      {
         Teeth = Teeth - food.ToothDecay();
      }
   }
}

 

namespace App.Stuff
{
   public class SweetFood : Food
   {
      public int ToothDecay()
      {
         return 10;
      }
   }
}

 

namespace App.Stuff
{
   public class SourFood : Food
   {
      public int ToothDecay()
      {
         return 2;
      }
   }
}

 

namespace App.Stuff
{
   public class SaltyFood : Food
   {
      public int ToothDecay()
      {
         return 0;
      }
   }
}

 

namespace App.Stuff
{
   public class BitterFood : Food
   {
      public int ToothDecay()
      {
         return 1;
      }
   }
}

In the above left the enum/switch will ultimately lend itself to refactorings which break the Open Closed Principal paradigm. When you add pungent and astringent you have to change the enum and the switch. This is not so with the code on the right! Other things I learned were:

  1. John Teague suggested that polymorphism could be done, either with generics or subtype inheritance stuff as seen above. Honestly, this is in conflict with what I said above about different method signatures. I guess my prior understanding was weak.
  2. The Liskov Substitution Principle demands that one be able to "treat" all subtypes as the base type as shown above.
  3. Order.LineItems.Count is an ambiguous puzzle with regards to The Law of Demeter which suggests that the "two dot rule" may reveal a code smell. If an Order does not necessarily have LineItems and the Count property may thus reveal if LineItems exist, then the Count may be of Order and not LineItems and hence not a violation. Otherwise the two dots reveal a violation.
  4. The Factory and Template patterns were discussed some as variations on the above. The factory way breaks with OCP to allow a case/switch to dole out particular subtypes and serves as a chokepoint so that only the factory is asked for subtypes.
  5. I learned about abstract classes last night.
    namespace App.Stuff
    {
       public abstract class Fish
       {
          public abstract int Bite();
       }
    }
     
    namespace App.Stuff
    {
       public class Gar : Fish
       {
          public override int Bite()
          {
             int teeth = 100;
             return teeth;
          }
       }
    }

     
     
    This should look to you a lot like an interface inheritance implementation.
    namespace App.Stuff
    {
       public interface Fish
       {
          int Bite();
       }
    }
     
    namespace App.Stuff
    {
       public class Gar : Fish
       {
          public int Bite()
          {
             int teeth = 100;
             return teeth;
          }
       }
    }

No comments:

Post a Comment