Monday, 22 December 2014

DESIGN PATTERN BY EXAMPLE (IN RUBY) - ABSTRACT FACTORY AND SINGLETON PATTERN

Hi everybody and welcome back.

In my first and second article I have shown you how to refactor existing code to use Template, Strategy and Observer design patterns. 


Today, I am going to show you how to refactor an existing application to use the Abstract Factory and the Singleton pattern.

The application we are going to explore today, is a very simple application. It contains a Client class that uses several Product classes, ProdA, ProdB and ProdC. 

The client class will instantiate some Products and call some methods on these products.


 
class Client   
  def initialize @prod_A = ProductA.new  
  end def foo @prod_A.do_your_stuff  
  my_prod_B = ProductB.new  
  my_prod_B.do_it  
  @prod_C = ProductC.new  
  @prod_C.perform  
  end
end  

class ProductA  
  def do_your_stuff puts "I'm a ProductA, doing my stuff" 
  end
end  

class ProductB  
  def do_it puts "I'm a ProductB, doing it" 
  end
end  
 class ProductC  
  def perform puts "I'm a ProductC, performing" 
  end
end  
 
my_client = Client.new  
my_client.foo  

As you can see, the binding between the Client and Product classes is very tight; The Client class specifically names and creates the Product classes it uses. 

This is one of the scenarios where refactor to Factory Pattern removes the coupling between the Client and the various Product classes. 
Also, we are going to extend this application to create several families of products, therefore the Abstract Design Pattern will better suit this use case.

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part of the theme. The client doesn't know (or care) which concrete objects it gets from each of these internal factories, since it uses only the generic interfaces of their products. This pattern separates the details of implementation of a set of objects from their general usage and relies on object composition, as object creation is implemented in methods exposed in the factory interface.
Thus, we create an Abstract factory class containing three “abstract” methods, one for each product creation that the main application.

 module AbstractProductFactory  
  def create_product_A  
   puts "You should implement this method in the concrete factory"  
  end  
  def create_product_B  
   puts "You should implement this method in the concrete factory
  end  
  def create_product_C  
   puts "You should implement this method in the concrete factory"  
  end  
 end  

The implementation for these methods will raise an exception because it will be responsibility of the specific Factory extending this Abstract factory to create an instance of a specific family of product.

In this same class we defined a concrete ProductFactory extending the AbstractFactory class and overriding the three creation methods for creating products. 
Each of these method will create a different product (productA, productB and ProductC) belonging to the same product family. We will call this family "CoolProduct" to distinguish from the other families.

 class CoolProductFactory  
  include AbstractProductFactory    
  def create_product_A  
     CoolProductA.new  
  end  
  def create_product_B  
     CoolProcductB.new  
  end  
  def create_product_C  
     CoolProductC.new  
  end  
 end  

The Client will change to accept a factory in his constructor. It will never instantiate a product by itself anymore. Whenever it will need a product of a certain family and type, it will ask the factory to provide one. 
In the following piece of code, the client now ask the factory to create three different products. Applying this simple pattern we have decoupled the Client from the different Product. The responsibility of creating different family of product has been moved in the correspondent factory.


 require_relative 'abstract_product_factory.rb'  
 class Client  
   attr_accessor :productFactory  
   def initialize(productFactory)  
     @productFactory = productFactory  
     @prod_A = @productFactory.create_product_A  
     @prod_B = @productFactory.create_product_B  
     @prod_C = @productFactory.create_product_C  
   end  
   def foo  
     @prod_A.do_your_stuff  
     @prod_B.do_it  
     @prod_C.perform  
   end  
 end  
 coolClient = Client.new(CoolProductFactory.new)  
 coolClient.foo  


We have seen that the only responsibility of the factory is to create Product objects on demand. The factory doesn't keep any state. That said, a simple question should come in your mind; Do we need to create a different factory for each client using this application ? The answer is no, we don't need to. 
We need only one instance for each factory and we can reuse it in each clients that needs it.

This is the classic scenario where the Singleton pattern comes in help.

In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. 
There are different possible implementation of the singleton pattern. We decided to use the one best suits this scenario. We defined a class variable called @@instance in the factory itself and we assigned an instance of the factory class to it. We created a method self.instance to return this specific instance (and this instance only) when need by the client. We used the code private_class_method :new to prevent any other classes from instantiate this class again. 
This assured us that we will always have one and only one instance of this Factory. Exactly what we wanted.

  class CoolProductFactory   
  include AbstractProductFactory   
  @@instance = CoolProductFactory.new   
  private_class_method :new   
  def self.instance   
   return @@instance   
  end   
  def create_product_A   
   ProductCoolA.new   
  end   
  def create_product_B   
   ProductCoolB.new   
  end   
  def create_product_C   
   ProductCoolC.new   
  end   
  end   

Now we can construct the client object just retrieving a concrete factory instance calling the method "instance" on the FactoryClass we need, and pass it to the client:


 coolClient = Client.new(CoolProductFactory.instance)  
 coolClient.foo  

What if a new family of products needs to be added to the application ? 

Easy, if you used the Abstract Factory Pattern correctly!

Let's say a new family of products called UncoolProduct comes into play.
We need to add three classes, one for each product, of this family. These products had their own behavior. They all look very similar to this one:


 require_relative 'product_a.rb'  
 class UncoolProductA < ProductA  
  def do_your_stuff  
   puts "I'm a UncoolProductA, doing my stuff"  
  end  
 end  

UncoolProductA extends a class called productA which is extended also by the class CoolProductA. 
The same is valid for UncoolProductB and UncoolProductC.

We added a new concrete factory implementing the Abstract Factory. 
This new concrete factory will have the responsibility of creating products for this new product family. 
Also in this case, this factory will be a singleton.

 class UnCoolProductFactory  
  include AbstractProductFactory  
  @@instance = UnCoolProductFactory.new  
  private_class_method :new  
  def self.instance  
   return @@instance  
  end  
  def create_product_A  
   UncoolProductA.new  
  end  
  def create_product_B  
   ProductUncoolB.new  
  end  
  def create_product_C  
   UncoolProductC.new  
  end  
 end  

A new Client instance, the UncoolClient, will be created requiring an instance of the UncoolProductFactory. The client will now ask its factory to produce three uncool products.


 unCoolClient = Client.new(UnCoolProductFactory.instance)  
 unCoolClient.foo  

Also, we observe that:

  • There will be two concrete Factory classes, cool and uncool. They may or may not have a common superclass.
  • Each Factory class will contain a certain number of methods for creating a certain number of products.
  • For each product (A, B and C) there'll be two concrete subclasses (CoolProduct, UncoolProduct).
  • The main application should create the factory instance that is used to instantiate the Client.

The latest point is probably the most important. It shows that the client can operate on different product families simply swapping in and out the concrete factory used. Basically our client can be a CoolClient or an UnCoolClient simply receiving the correspondent factory from the class that creates it.

Below there is a class diagram for this application:




HAPPY CODING !!
LUCA

No comments:

Post a Comment