Wednesday, February 8, 2012

Two very common design patterns to show how to think through simple software design challanges.

Design Pattern is perhaps the mostly used buzz word for software professionals. The geeks of the industry always talk about it and the novices got tempted to learn design patterns reading the theory. There are lot of books and articles on different design patterns. In many interviews you may learn questions like do you know design pattern? Do you know XXX design pattern? Thus perhaps many software developers do have a look on design pattern tutorials before any interview to face. But really, you cannot learn design pattern in this way. As a matter of fact design pattern is not something to learn.

Design Pattern is not something to learn! Yes. Design patterns are nothing but some great ways to solve some classic software design scenarios. If you know the principles of software design and give effort to solve the design challenges in your projects, you will find, eventually you have used one or more design patterns even without knowing about that. And this is the way all the design patterns evolved. If a great software designer solves a design challenge there is every possibility, a design pattern will be used or may be a new design pattern will be created!

To me design patterns are some guidelines that all software developers should know so that when he works in any project he can solve the design challenges easily. So, it is more to be habituated and practiced than to be learned. When you read about any design pattern you should try to grasp what are the problems that design pattern is trying to solve and what was the thinking process to come up with that design pattern. And when you work in your project, before jump to code you need to find what are the design challanges exist in your work. Then, you should solve those design challenges and then start coding. During thinking about your design challenges if you remember any similar challenge that you found while reading about any design pattern go back to that, have a look on that and if it really solves your problem use that. Or perhaps use that in a way that suits in your scenario. The more you practice this procedure, less you will go back to read about patterns and better your designs will become.

I think you have already realized that, this write-up is just yet another write-up on Design Patterns. Design patterns are not any technology. Design patterns are actually some patterns of thinking to come up with a flawless design of your software requirements. Here I will try to derive towards 2 of the most frequently used design patterns from some scenarios. I will not tell the name of the patterns at the beginning of the examples so that your reading does not become tempted by some existing theoretical knowledge on these design patterns. You will find the pattern name at the end of each discussion.

Pattern 1: Let’s say you have developed a new operating system for smart phones named “Smart OS”. Now you want general people to use smart phones using this operating system. What will you do? You will create a specification for the OS and some instructions to manufacture the phone.

Let’s assume all the people of the world are capable enough to manufacture a phone using a spec. So, if you want to sell the OS to 1000 persons, the solution is all of those 1000 persons will learn the spec, create the phone and use it. Does this sound practical? Now, if you release a new version of "Smart OS"? Will all of those 1000 person learn the new spec again and update their phone? What if you need more customers? It really sounds weird. What is the solution then? I am sure you all know the solution.

For your new OS you will release the spec to a phone manufacturing company or maybe you will form one of your own (Like Apple builds iPhone on top of their own OS). The company will manufacture the phone using your OS and the end users will buy this from the manufacturer. So what should be the expected benefits here?

  1. Only the manufacturer company will need to know how to build the phone.
  2. If the spec changes, the manufacturer will learn this and will distribute this to all the customers.
  3. The price becomes less.
  4. The end user will just use the phone and will not need to know how this is created.
I am sure already you are thinking the example is too obvious. But similar scenario comes when we develop software. Sometimes, the situations are not too obvious.

For example, you are required to develop an html report generator. If you jump start to the code every possibility is that, you will create a class named HtmlReportGenerator. That will have a method named GenerateReport. This method will implement the report generation functionality. The client app will create an instance of the Html report generator class and will use the GenerateReport function to create the report.  

Ok! That works fine. Now, your client wants a pdf report from you. What will you do?

Option 1: Create a PdfReportGenerator class.
Option 2: Re-factor the code. Remove HtmlReportGenerator. Instead create a general class
ReportGenerator. Create 2 methods. GenerateHtmlReport and GeneratePdfReport.
Option 3: Re-factor the code. Remove HtmlReportGenerator. Instead create a general class
ReportGenerator. Create the function GenerateReport as GenerateReport(ReportType rptType) taking report type as parameter.

Well, now if client wants reports in word format, xl format and few more formats, what will you do? Will you continue re-factoring? Or, will continue changing your report generator function? If you know the standard software design principles, these approaches will be a violation of Open-Close principle and Single Responsibility principle. Overall this will be a complete mess (BBOM).

Now, let's relate this with smart phone OS scenario. Though it was not very obvious, the Report generator example really has similarity with phone OS. So how could be the solution?

Simple! You create a company who will serve you the reports (As like mobile manufacturer company). You will serve data to the company and will tell which format of report you want. The company will deliver you the report. If new report format comes to the scenario, the company will learn how to do that and as a client you have to do nothing but ask for the correct type of report. See the code example below.

Now, using this design you have achieved the following benefits when a new report format needs to be added:

  1. You do not need to modify the ReportGenerator base class.
  2. You do not need to know how other report formats are handled.
  3. You do not need to know how to get instance of the report generator of your desired format.
  4. Your code becomes clean and free from BBOM.
Here you have one class named "ReportGeneratorCompany". As like the mobile phone manufacturer company for "Smart OS", ReportGeneratorCompany will know which format of report you need (The OS), It will create a report generator for you (perhaps the phone manufacturer team for "Smart OS" ) and the report generator will generate the report for you.

So, in both case ("Smart OS" and Report Generator), we saw how we can handle the scenario in smarter way. And believe me whenever, you do anything smartly in software, you are using a design pattern or as I said perhaps you are creating a new design pattern. I am sure many of you already have realized which design pattern I had used for report generator. Yes. This is Factory Pattern. Factory pattern is one of the widely used and simplest object creational design patterns. If the name of the "ReportGeneratorCompany" class were "ReportGeneratorFactory", it would have been more obvious to you. 

Here, the concept of the factory pattern is, you do not create instance of your object directly, rather you abstract away the instantiation logic in a factory class. When you need your object, you will ask the factory class and the factory class will provide you the instantiated object.

There are few other creational design patterns widely accepted as standards in software world. Those are: Factory Method, Abstract Factory, Builder, Prototype, etc.  In this write up we will discuss on 1 more creational design pattern. As before I would not tell now which one is that, rather I would like to derive to that pattern from design challenges.

Pattern 2: Let’s talk about the “Smart OS” again. Say in your smart phone OS you have some location specific features. For example you have location specific language support, you have location specific theme support, you have location specific hardware support etc. When you develop your OS, you listed out the features you are going to provide in your OS.  Now, the situation comes where few features will have different implementation based on your location. How will you handle this scenario?

Option 1: One way to solve this situation is have conditions in your Service provider classes (Language Service Provider, Theme Provider etc) and load the services based on the condition. In this case you have to provide the location information to the service providers.

What is your opinion on the above approach? I can see the following problem here:
  1. If a new location needs to be added we will have to modify the Service classes, which is a clear violation of Open-Close Principle.
  2. It is possible to load the OS with language from USA and theme from UK, which should not be allowed. 
  3. Code is un-organized and no point of extension is available.
This type of scenario comes every now and then in our software development practices. Here requirement shows that few objects need to be instantiated as a group. That means, if the location is USA, you must load all the USA based services for your device. So, you must enforce this.
In our previous pattern (Factory pattern) we saw how we can separate the object creation logic from the client using a factory. Should we be able to do something similar for the location based services? Well Let us try.

1. First of all we will need to abstract away the definition of the services. So, let’s create 2 abstract classes for language service and theme service.
2. Create concrete classes for different services based on location, inheriting from the abstract service classes. For example UsaThemeService class and UkThemeService class inheriting from ThemeService abstract class.

3. Create an abstract factory class which will have abstract definition of the methods to create all the services that needs to be created based on location.
4. Create concrete factory classes inheriting from the abstract factory class to create specific location based services.

5. Create the class for LocationBasedService. This class will create all the services instances based on the location provided and will load the services.
6. The client will know the location and will create service factory of that specific location. Eventually client will load the services.

The above way of implementation can further be improved using few more patterns perhaps like strategy, DI etc. However, that is out of the scope of this discussion.

So, we have created the location based services for the “Smart OS” in a new approach than the 1st approach we described. The new approach tries to solve the design challenges that we had on the first approach.
  1. Now, adding a new location based service is easy. You implement the services for the new location and as you already know what are the services you will need to implement from the LocationBasedServiceFactory you will not miss anyone. 
  2. This implementation will enforce you to initiate all the services of the same location and will prohibit you to initiate services from mixed locations.
  3. Service instantiation is abstracted away from the service itself, so, the client of the services do not need to know how to instantiate the services. 
  4. Your code has become extensible and manageable.
I am sure many of you already realized which pattern I have used here. Yes the pattern is “Abstract Factory”.

I would like to summarize the abstract factory pattern as it is little more complicated than the simple factory pattern. Following are the participators in the abstract factory pattern.

AbstractFactory – Abstract class to declare operations (LocationBasedServiceFactory) that create abstract products (theme service, language service).
ConcreteFactory – Implements AbstractFactory class to implement operations to create concrete products (UsaBaseServiceFactory).
AbstractProduct - Declares an abstract class for a type of product objects (ThemeService).
Product - Defines a product to be created by the corresponding ConcreteFactory; it implements the AbstractProduct class.
Client - uses the abstract classes declared by the AbstractFactory and AbstractProduct classes.

In this article, I have derived to explain 2 creational design patterns with examples. My goal was to help you gearing your thinking process to solve a problem in more patterned way. I hope to write on few structural and behavioral patterns in coming days too. More practice you do to achieve the best architecture for your work, the better your architectures will be.

I will appreciate your comments and feedbacks on this.

No comments:

Post a Comment