As we already discussed, in Object Oriented Programming, one of the majorly used principles is that of Inheritance (apart from Polymorphism). The team is assigned an application to write for the project. The idea of the application is drawn out on a whiteboard and connections are drawn. And soon afterwards, coding begins. Base classes are written and sub-classes extended. And then at some point of time, the “client” sends a request for changing a behavior. This is when everything starts falling out. The coders get into an arduous task of changing the class code that defines behavior for not only the base class, but also the sub-classes, not forgetting to mention the innumerable times unit-tests are run to ascertain the proper behavior.
Taking the topic further from where we left it, what if more shape classes have to be added? For that, not only the specific Shape classes (like, Triangle, Quadrilateral, Parallelogram, Ellipse etc) will have to be added, but the ShapeClient source will also need to be modified. Further, imagine the ShapeClient class to be a big class with lots of functionality. Generally, the developer who wrote this code would understand it best. However, this would become a nightmare for another developer who would have to understand the code and make changes to it.
To make the point clearer, let us take a look at a different solution to the above problem, below:
Introduce a protocol to abstract the draw method definitions for each of the shape types:
@protocol ShapeProtocol <NSObject>
– (void) draw;
public interface IShape
public void draw();
Extend Shape to inherit from ShapeProtocol
//declare Shape class in Shape.h
@interface Shape : NSObject <ShapeProtocol>
//property to access Shape type
@property (nonatomic, assign) ShapeType shape;
• Provide a default implementation of the ‘draw’ method in the ‘Shape’ class
• Implement changes in the ShapeClient class – This is the most important aspect of the solution as this provides the ShapeClient class object to just invoke the ‘draw’ method on any type of a ‘Shape’ object
• Implement draw methods for different shape types. Here, developers write their own implementation of different shape types.
Result: The new design provides greater flexibility to add new Shape classes, without modifying existing code. Developers now need to just write the new Shape class and provide within itself the ‘draw’ functionality. The ‘ShapeClient’ class object can invoke the ‘draw’ method on the ‘Shape’ type class (or rather subclass) without having to worry about what kind of shape is the class object.
What has been showed above is an implementation of a principle called the ‘Open Close Principle’. The ‘Open Close Principle’ states that code should be designed in a way to allow addition of functionality or behavior with minimum changes to the existing system. In other words, code should be “open for extension”, but “closed for modifications”.
So, the take-away from the above example is that of the particular situation in question and its proposed solution. Since the solution provides ease of extension and can be replicated in similar other situations, a pattern is observed.
Design Patterns are similarly a set of techniques or principles that provide a reusable solution to a commonly occurring problem. Applying design patterns to software solutions helps by:
• Providing known solutions
• Reducing development time
• Allowing developers to write well defined and cleaner code
• Improve communication of development teams
Let us look at a few scenarios:
• You want to write a custom Log class that will throw some output to the console. A global function can be written for the same, but methods/functions have little, if not nothing, to do with object-oriented programming. A custom class, if written can be instantiated and used to output the desired log. But that would require either the instance to be shared within the application context, or instantiate the class object every time a log has to be sent to the console. Using the Singleton pattern, a static instance of the class can be exposed via a class method allowing invocation of the Log statement from anywhere in the code, while at the same time maintaining a good code structure.
• You have access to a library (not source code), but to which you want to add certain functionalities. The simplest option is to use class inheritance and extend its behavior. But that would mean using the derived class object in place of the base class object everywhere it is referenced in the codebase. Extrapolate this to a large project and you end up having a large number of subclasses to support combinations of such extended features. But how would you just extend behavior without modifying existing code? Using a design pattern called the Decorator pattern, it becomes easy to add responsibility to individual objects dynamically and transparently without affecting existing ones.
• Consider a scenario where objects can determine if they can handle an event. If the object cannot handle the event, it will pass the event to another object to handle. Typically, different objects can be written that handle specific events and there would be loose coupling between them. Using this technique, the developer can quickly run into issues about managing code and maintaining the sequence in which events have to be passed between objects. Using the Chain of Responsibility pattern, a stack like structure is setup, wherein events are either handled or sent up the “chain”. Any number of objects with additional features can be simply “chained” together thus increasing capability.
Needless to say, design patterns bring structure and order to code, apart from taking away developers’ headache from hours of analyzing badly written apps. However, once implemented (in code), it can be used in multiple applications, and that is the biggest advantage of using design patterns.