Intro
Now days, any programmer working with a language which conforms to the OOP paradigm, understands the power of Design Patterns (DP). A variety of great books are available for plain English interpretations for the various 23 GoF design patterns and the differences between them. Yet, there are still many controversies regarding how certain patterns differ and how they are related.
Design Patterns, at first glance, sometimes appear very similar, not to say identical. When searching answers in forums, articles and blogs, I have encountered in a wide range of responses, starting from "It's the same DP and the only difference is the intend of the developer" and reaching "the questioned DPs are completely different and I can't even understand your question". If you sense that the truth lays somewhere in the between those responses, this new series of posts might interest you.
This post is a first in a new series of posts, in which I will try to highlight specific particular parts (henceforth: 'Hot Spots') of the discussed DP, differentiating them from each other.
At times it will be the implementation which will make the difference, but almost always I will prove that 'similar' DPs where designed for completely different purposes.
In this post, I explain the relevant Design Patterns only when it is absolutely required in order to explain the difference between the various DPs, otherwise a link to a good article is supplied.
For the sake of this series, I am considering only the DPs listed in the original GoF book.
- Inheritance
- Composition
- Inversion of Control & Dependency Injection (IoC & DI)
Inheritance vs Composition
Inheritance
When we first started learning OOP, one of the most important lessons we were taught was Inheritance. Although Inheritance is a critical OOP subject, it is almost never the best solution for your design issues. Inheritance couples the base class with it's inherited classes. If, for any reason, the base class implementation must change, all of it's subclasses, who use or override their base methods, will be affected.
If the base is an Interface, then every single additional method added to the interface will require adding the implementation to all of it's subclasses, even those that were not meant to implement the new implementation. i.e:
interface IRobot { void Talk(); void OpenDoor(); void CloseDoor(); //* New capability void Walk(); } class ChefRobot : IRobot { public void Talk() { //* Implementation } public void OpenDoor() { //* Implementation } public void CloseDoor() { //* Implementation } //* The application business logic dictates that ChefRobot doen't have the need to walk. //* Walk() remain unimplemented. public void Walk() { throw new NotImplementedException();//* EXCEPTION !! } } class BabySitterRobot : IRobot { public void Talk() { //* Implementation } public void OpenDoor() { //* Implementation } public void CloseDoor() { //* Implementation } public void Walk() { //* New Walk capability implementation } }
"IRobot" has several methods that must be implemented by every Robot subclass. Suppose a time comes, and we decide to add a new 'Walk' capability.
Adding Walk()
method to "IRobot" forces us to implement Walk()
method in every Robot subclass. The problem is that our application's business logic dictates that "ChefRobot" doesn't have the need to walk. By using Inheritance we are forced to add the method Walk()
in "ChefRobot" and leave it unimplemented.
Composition Instead
Inheritance may be considered only on two conditions:
- The relationship between the base and subclasses is stable by definition. There are no reasonable scenarios in which this relationship should break.
- When the interfaces are kept very small to include only the minimum set of methods required to group the various classes implementations.
Additionally, inheritance can be used to solve an OOP design issue, and not to represent a hierarchic structure. The coupling can generally be tolerated because we are sure enough the overall design will not change on a daily basis.
In any other case composition should be considered.
Composition means that instead of inheriting the common functionalities from a common base class, the common functionalities are encapsulated in a dedicated class and made exposed via public properties.
Now, the developer has the flexibility to add/remove common functionalities as required, without influencing the entire architecture.
Reviewing our "IRobot" sample again, we should have left in "IRobot" only the smallest set of common capabilities for all robots, and only add the new "Walkable" class with a Walk()
method. This class may be referenced by all the Robots that can walk.
interface IRobot { void GetBatteryStatus(); void PowerOn(); void PowerOff(); } class ChefRobot : IRobot { public TalkCapability TalkProperty { get; set; } public DoorHandleCapability DoorProperty { get; set; } public void GetBatteryStatus() { //* Implementation } public void PowerOn() { //* Implementation } public void PowerOff() { //* Implementation } } class BabySitterRobot : IRobot { public WalkCapability WalkProperty { get; set; } public TalkCapability TalkProperty { get; set; } public DoorHandleCapability DoorProperty { get; set; } public void GetBatteryStatus() { //* Implementation } public void PowerOn() { //* Implementation } public void PowerOff() { //* Implementation } }
When using a composition, there is merely a weak relationship between the implementation and the class hosting the implementation. Because the class only contains the implementation, any change to the class containing the implementation will not affect the implementation itself and vice-versa. In addition, classes that should not make use of a specific implementation will simply not include it.
Alert readers may be confused from this last statement, because my solution prevents developers from using all "Robot" subclasses in a unified way.
We are no longer able to call the Walk()
method on an instance of "IRobot" because the Walk()
method is not a part of the common interface anymore:
IRobot r = new BabySitterRobot(); r.WakCapability.Walk(); //*Compilation ErrorTo solve this kind of issue, where some objects need to be extended without influencing the entire architecture, we need to use a relevant, suitable DP (i.e. Decorator), but not via Inheritance.
IoC & DI
Inversion of Control (IoC) is a technique very popular for solving design issues (I am sure you already use it, maybe without knowing it's professional name).
In order to fully implement it's goal, IoC uses another sub-technique called Dependency Injection (DI).
IoC
In brief, IoC is used when a method's internal implementation relies on some other class capabilities.
Without IoC we could simply instantiate the required internal class and use it's capabilities whenever it was needed.
The problem with this approach is that the two classes are now tightly coupled:
In the example below, "BatteryStatus" is welded inside "ChefRobot". The GetBatteryStatus()
method can only work with a specific fixed instance of the "BatteryStatus" class. If, for any reason, we would need to switch the behavior at runtime, occasionally instantiating a new "NiceBatteryStatus" class will become a living nightmare. In addition, any change to the "BatteryStatus" class will require changes in ChefRobot.GetBatteryStatus()
.
class ChefRobot : IRobot { .... public void GetBatteryStatus() { //* ChefRobot & BatteryStatus are tightly coupled BatteryStatus bs = new BatteryStatus (params); bs.DoSome(); //* More work }
The goal of IoC is to decouple the concrete class instantiation from it's clients. This is done by encapsulating the creation of the class outside of our exposed method, thus making the exposed method use an external object.
In other words we are inverting the instantiation control to the ChefRobot's clients.
By doing so, "ChefRobot" is dependent upon the class interface but doesn't know the concrete implementation. The implementation may now vary.[see next sample]
DI
The Dependent class can be Injected (from here the name Dependency Injection) into it's client in three different ways:
- Interface DI - The exposed method will be refactored to receive the class' interface as a parameter and just call upon methods.
- Setter DI - The exposed method's class will add a new Setter method of "IClass" type. The concrete "IClass" object will be injected into this Setter method.
- Configuration DI - The exposed method will, internally, call on a configuration file that specifies the exact concrete type to instantiate.
class ChefRobot : IRobot { .... //* Setter DI public IBatteryStatus BatteryStatus{ get ; set ; } public void GetBatteryStatus() { //* ChefRobot & BatteryStatus are NOT tightly coupled. //* Any IBatteryStatus object can be used //* Now the control of IBatteryStatus instantiation is inverst BatteryStatus.DoSome(); //* More work }
Equipped with this information, we are now ready to understand some DP Hot-Spots. Within a couple of days I will post the first article of this series: Factory Method vs Abstract Factory.
Comments