Labels

Friday, December 3, 2010

Design Principles - SOLID - Proof Of Concept

Open Close Principle - OCP

Principle - Software entities like classes, modules and functions should be open for extension but closed for modifications

 

Consider below code.  The LogOn function must be changed every time a new kind of modem is added to the software.

Worse, since each different type of modem depends upon the Modem::Type enumeration, each modem must be recompiled every time a new kind of modem is added.

 

 

 

struct Modem

{

  enum Type {hayes, courrier, ernie) type;

};

 

void LogOn(Modem m,string pno)

{

  if (m.type == Modem::hayes)

    DialHayes((Hayes&)m, pno);

  else if (m.type == Modem::courrier)

    DialCourrier((Courrier&)m, pno);

  else if (m.type == Modem::ernie)

    DialErnie((Ernie&)m, pno)

 

// ...you get the idea

}

 

 

 

 

Of course this is not the worst attribute of this kind of design.

Programs that are designed this way tend to be littered with similar if/else or switch statement.

Every time anything needs to be done to the modem, a switch statement if/else chain will need to add/select the proper functions to use.

When new modems are added, or modem policy changes, the code must be scanned for all these selection statements, and each must be appropriately modified.

 

Thus here - Logon, is modified to be extended – Breaking OCP.

 

OCP Solution :

 

 

 

 

class LogOn

{

  public static void LogOn(Modem m)

  {

    m.Dial();

  }

}

class MainApp

{

  static void Main()

  {

                LogOn.LogOn(new HayesModem());

                LogOn.LogOn(new CourierModem ());

                LogOn.LogOn(new ErnieModem ());

  }

}

 

 

Here the LogOn function depends only upon the Modem interface.

Additional modems will not cause the LogOn function to change.

Thus, we have created a module that can be extended, with new modems, without requiring modification.

 

 

Interface Segregation Principle - ISP

Principle - Clients should not be forced to depend upon interfaces that they don't use.

 

Below shows a class with many clients, and one large interface to serve them all.

Note that whenever a change is made to one of the methods that ClientA calls, ClientB and ClientC may be affected.

It may be necessary to recompile and redeploy them. This is unfortunate.

 

 

 

 

 

 

class Service : IServiceA,IServiceB,IServiceC

    {

        public string Swim()

        {

            return "Animal Swimming";

        }

 

        public string Fly()

        {

            return "Animal Flying";

        }

 

        public string Run()

        {

            return "Animal Running";

        }

    }

class ClientA

{

   Service service;

 

   public ClientA(Service service)

   {

        this. service = service;

   }

 

   public string Execute()

   {

      return running.Swim();

      return running.Fly ();

      return running.Run();

 

   }

}

 

class ClientB

{

   Service service;

 

   public ClientA(Service service)

   {

        this. service = service;

   }

 

   public string Execute()

   {

      return running.Fly();

      return running.Swim();

      return running.Run();

 

   }

}

 

class ClientC

{

   Service service;

 

   public ClientA(Service service)

   {

        this. service = service;

   }

 

   public string Execute()

   {

      return running.Run();

      return running.Swim();

      return running.Fly();

   }

}

 

Here you can see that: Even the ClientA, does not the need the Fly and Run services, it can access those.

Thus if any change is made in Fly and Run, CleintA also need to be changed.

 

Same applies for CleintB & C.  This breaking the rule.

 

 

 

A better technique is shown below. The methods needed by each client are placed in special interfaces that are specific to that client.

Those interfaces are Multiply Inherited by the Service class, and implemented there.

Now, If the interface for ClientA needs to change, ClientB and ClientC will remain unaffected. They will not have to be recompiled or redeployed.

 

 

 

 

 

 

public interface IServiceA

{

   string Swim();

}

 

public interface IServiceB

{

   string Fly();

}

 

public interface IServiceC

{

   string Run();

}

 

 

class Service : IServiceA,IServiceB,IServiceC

    {

        public string Swim()

        {

            return "Animal Swimming";

        }

 

        public string Fly()

        {

            return "Animal Flying";

        }

 

        public string Run()

        {

            return "Animal Running";

        }

    }

 

 

class ClientA

{

   IServiceA running;

 

   public ClientA(IServiceA running)

   {

        this.running = running;

   }

 

   public string Execute()

   {

      return running.Swim();

   }

}

 

class ClientB

{

   IServiceB animal;

   public ClientB(IServiceB animal)

   {

      this.animal = animal;

   }

 

   public string Execute()

   {

       return animal.Fly();

   }

}

 

class ClientC

{

  IServiceC animal;

 

  public ClientC(IServiceC animal)

  {

     this.animal = animal;

  }

 

  public string Execute()

  {

     return animal.Run();

  }

}

 

Here you can see that: The ClientA, has access to only the relevant Swim service. It cannot access the Fly and Run services.

Thus if any change is made in Fly and Run, CleintA will not be affected.

 

Same applies for CleintB & C.  This serving the ISP rule.

 

 

 Hope this helps.

 

Thanks & Regards,

Arun Manglick

No comments:

Post a Comment