Abstract factory pattern

The Universal Call Center company is growing very fast, and they have a small Force.com application where agents can log a request to place an order for a new computer. Developers have just learned about the factory method design pattern and are very excited to implement the same design pattern in this scenario as well. They decided to create an interface of the computer type and its different implementations, such as high-configuration and low-configuration computers. However, they were disappointed when they heard that they need to support various types of configurations other than low and high configurations. For example, some computers need high end processors but small monitors and less storage. There were a few requests regarding an average processor but an LCD monitor and SSD storage.

Abstract factory pattern

By this time, developers realized that the factory method pattern cannot be used in this scenario. Let's see how they solved this problem.

The abstract factory pattern is used to return a set of factories that when combined together represents a related product. Unlike the factory method pattern, which returns a concrete class, the abstract factory pattern returns another factory. This pattern is used to produce one big functionality by combining many factories.

The provides a way to encapsulate a group of individual factories that have a common theme without depending on concrete classes.

Let's start with processors. Every processor performs the CPU operation with a difference in speed. We can create an interface that can be implemented by all concrete classes, as shown in the following code snippet:

interface IProcessor{ void performOperation(); } 
 
class HighEndProcessor implements IProcessor{
public void performOperation(){

System.debug('Super fast Processor');
}
}

class AverageProcessor implements IProcessor{
 public void performOperation(){

System.debug('Average Speed Processor');
}
}

In the same way, every hard disk is used to perform a storage operation:

interface IStorage {void storeData(); } 
 
class SSDStorage implements IStorage{ 
  public void storeData(){ 

    System.debug('Storage Operation is performed in Solid State Drive'); 
  } 
} 
     
class HDDStorage implements IStorage{ 
  public void storeData(){ 

    System.debug('Storage Operation is performed on HDD'); 
  } 
}      

A monitor can have the following structure:

interface IMonitor {void display(); } 
 
 
class LEDMonitor implements IMonitor{ 
        public void display(){ 

            System.debug('Display in 17inch LED Monitor'); 
        } 
    } 
     
     
class LCDMonitor implements IMonitor{ 
        public void display(){ 

            System.debug(Display in LCD Monitor'); 
        } 
    } 

Until this point, we have different concrete classes for a monitor, storage, and processor. As an extra step, we can create factory classes as well, which will return appropriate concrete classes. However, to assemble a computer, we need to combine all the three factories to produce one result. A computer may be of the HighPerformance or StandardConfiguration type, which will have different combinations of a monitor, storage, and processor.

We can come up with an interface, which can be implemented by all factory classes of a computer:

interface IComputerFactory { 
      IProcessor getProcessor(); 
      IStorage getStorage(); 
      IMonitor getMonitor();         
    } 
     
public class HighPerformance implements IComputerFactory{ 
        public IProcessor getProcessor(){ 
            return new Quadcore(); 
        } 
        public IStorage getStorage(){ 
            return new SSDStorage(); 
        } 
        public IMonitor getMonitor(){ 
            return new LEDMonitor(); 
        } 
    }   
   
public class StandardConfiguration implements IComputerFactory{ 
        public virtual IProcessor getProcessor(){ 
            return new DualCore(); 
        } 
        public virtual IStorage getStorage(){ 
            return new HDDStorage(); 
        } 
        public virtual IMonitor getMonitor(){ 
            return new LCDMonitor(); 
        } 
    } 

We have two factory classes that produce computers by combining the other three factories. The last part is to assemble a computer from all these factories, which can be used by any client application.

Programming languages, such as Java or C# provide you with the concept of a package and namespace, respectively. This is a very useful technique used to organize all related sets of classes at the same place. Unfortunately, Apex doesn't offer this functionality. However, we can mimic it up to some extent using the inner class, also known as the Apex nested class.

Note

Java developers can think of an Apex inner class as equivalent to a static nested class. As an inner class is a static class, it cannot access instance members of an outer class directly.

The following code snippet uses an Apex nested class to show how we can organize all the related code discussed till now:

public class AbstractFactory { 
     
    interface IProcessor{ void performOperation(); } 
    interface IStorage {void storeData(); } 
    interface IMonitor {void display(); } 
     
       class LEDMonitor implements IMonitor{ 
          
        public void display(){ 
            System.debug('Display in 17inch LED Monitor'); 
        } 

    }  
     
class LCDMonitor implements IMonitor{ 
          
        public void display(){ 
            System.debug('Display in LCD Monitor'); 
        } 

    }  
 
     
class SSDStorage implements IStorage{  
        public void storeData(){  
            System.debug('Storage Operation is performed in 
                          Solid State Drive'); 
        } 

    }  
      
class HDDStorage implements IStorage{  
        public void storeData(){  
            System.debug('Storage Operation is performed on HDD'); 
        } 

    } 
      
    class QuadCore implements IProcessor{  
        public void performOperation(){ 
            System.debug('Super fast Processor'); 
        } 

    } 
      
    class DualCore implements IProcessor{ 
          
        public void performOperation(){ 
            System.debug('Average Speed Processor'); 
        } 

    }   
 
    public interface IComputerFactory { 
      IProcessor getProcessor(); 
      IStorage getStorage(); 
      IMonitor getMonitor();         

    }  
     
    public class HighPerformance implements IComputerFactory{ 
        public IProcessor getProcessor(){ 
            return new Quadcore(); 
        } 
        public IStorage getStorage(){ 
            return new SSDStorage(); 
        } 
        public IMonitor getMonitor(){ 
            return new LEDMonitor(); 
        } 

    }  
       
    public class StandardConfiguration implements IComputerFactory{ 
        public virtual IProcessor getProcessor(){ 
            return new DualCore(); 
        } 
        public virtual IStorage getStorage(){ 
            return new HDDStorage(); 
        } 
        public virtual IMonitor getMonitor(){ 
            return new LCDMonitor(); 
        } 

    }  
     
    public class AssembleComputer 
    { 
        IComputerFactory computer ; 
        public AssembleComputer(IComputerFactory comp) 
        { 
            computer = comp; 
        } 
         
        public void testCompleteSystem() 
        { 
            IProcessor processor = computer.getProcessor(); 
            IStorage storage = computer.getStorage(); 
            IMonitor monitor = computer.getMonitor(); 
             
            processor.performOperation();

            storage.storeData();

            monitor.display(); 

        } 

    }  
 
} 

As you can see, all the related interfaces, concrete classes, and factory classes are part of one class called AbstractFactory; and therefore, the code is easy to maintain.

An example of code snippets used by a client application (an anonymous Apex in the developer console) is as follows:

AbstractFactory.IComputerFactory std = new AbstractFactory.StandardConfiguration(); 
AbstractFactory.AssembleComputer finalProduct = new AbstractFactory.AssembleComputer(std); 
finalProduct.testCompleteSystem(); 

The output will be as follows:

Average Speed Processor 
Storage Operation is performed on HDD 
Display in LCD Monitor 

The following class diagram shows you the structure of the abstract factory pattern used in the code:

Abstract factory pattern

A new perspective on the abstract factory pattern

In the preceding diagram, the HighPerformance and StandardConfigurations factory classes are tightly coupled and dependent on concrete classes of a processor, monitor, and storage. We can adopt the principle of a previously discussed design pattern, which is the factory method, to add a factory of each type and delegate the object creation to those classes. Currently, the HighPerformance and StandardConfiguration classes are responsible for instantiating a processor, monitor, and storage.

Let's redesign the preceding solution to add one additional layer of factory methods. In this approach, we cannot use nested Apex classes for concrete classes because nested classes cannot have static methods. So, we will have to create each concrete class as a top-level class.

To make a program as loosely coupled as possible, we need to create a factory class to instantiate concrete classes so that the client class can be independent of instantiating objects:

/** 
*  Factory class to return actual implementation of Processors 
*/ 
public class ProcessorFactroy{ 
     
    //Factory method to return instance of concrete class of processor 
    public static IProcessor getInstance(String processorType){  

         IProcessor retVal = null; 
         
        if(processorType == 'QuadCore'){ 
            retVal = new QuadCore(); 
             
        }else if(processorType == 'DualCore'){ 
            retVal = new DualCore(); 
             
        }          
        return retVal;          
    }     
}  

The preceding code snippet uses the Apex reflection to instantiate the class at runtime. In the same way, we need two more factory classes for a monitor and storage, as shown in the following code snippet:

  /** 
    *   Factory class to return actual implementation of Monitors 
    */ 
    public class MonitorFactory{ 
     
     //Factory method to return instance of concrete class of monitor 
    public static IMonitor getInstance(String monitorType){  
        IMonitor retVal = null; 
         
        if(monitorType == 'LEDMonitor'){ 
            retVal = new LEDMonitor(); 
             
        }else if(monitorType == 'LCDMonitor'){ 
            retVal = new LCDMonitor(); 
             
        }           
        return retVal;          
    }     
}  
 
 
/** 
    *   Factory class to return actual implementation of Storage 
    */ 
public class StorageFactory{  
     
    //Factory method to return instance of concrete class of Storage 
    public static IStorage getInstance(String storageName){ 
        IStorage retVal = null; 
         
        if(storageName == 'SSDStorage'){ 
            retVal = new SSDStorage();             
        }else if(storageName == 'HDDStorage'){ 
            retVal = new HDDStorage(); 
             
        } 
        return retVal;  
    } 
} 

To assemble a computer, we need to combine all three factories to produce one result. A computer may be of the HighPerformance or StandardConfiguration type, which will have different combinations of a monitor, storage, and processor.

We can use the same IComputerFactory interface here as well and implement that factory:

  
     
Public class HighPerformanceConfiguration implements IComputerFactory{ 
 
        public IProcessor getProcessor(){ 
            return ProcessorFactory.getInstance('QuadCore');  
        } 
 
        public IStorage getStorage(){ 
            return StorageFactory.getInstance('SSDStorage'); 
        } 
 
        public IMonitor getMonitor(){ 
            return MonitorFactory.getInstance('LEDMonitor'); 
        } 
 
    } 
 
          
 public class StandardConfiguration implements IComputerFactory{ 
 
        public IProcessor getProcessor(){ 
            return ProcessorFactory.getInstance('DualCore');  
        } 
 
        public IStorage getStorage(){ 
            return StorageFactory.getInstance('HDDStorage'); 
        } 
 
        public IMonitor getMonitor(){ 
            return MonitorFactory.getInstance('LCDMonitor'); 
        } 
 
  } 

We have two factory classes (HighPerformanceConfiguration and StandardConfiguration) that produce a computer by combining the other three factories. The only part remaining is to assemble a computer from all of these factories, which can be used by any client application:

public class AssembleComputer 
{ 
    IComputerFactory computer ; 
    IProcessor processor ; 
    IStorage storage ; 
    IMonitor monitor ; 
     
    /** 
     * Constructor to instantiate all factories 
     * */ 
    public AssembleComputer(IComputerFactory comp) 
    { 
        computer = comp; 
        processor = computer.getProcessor(); 
        storage = computer.getStorage(); 
        monitor = computer.getMonitor(); 
    } 
     
    /** 
     * run test to see if all factories are assembled together 
     * */ 
    public void runSystemDiagnosis() 
    {  
        processor.performOperation(); 
        storage.storeData();     
        monitor.display(); 
    } 
} 

The examples of code snippets used by a client application are as follows.

Snippet 1:

IComputerFactory highConfig = new HighPerformanceConfiguration(); 
 
AssembleComputer highConfig_Computer = new AssembleComputer(highConfig); 
 
highConfig_Computer.runSystemDiagnosis(); 

When the preceding code snippet is executed, you'll get the following output:

Super fast Processor 
Storage Operation is performed in Solid State Drive 
Display in 17inch LED Monitor 

Snippet 2:

IComputerFactory stdConfig = new StandardConfiguration(); 
 
AssembleComputer stdConfig_Computer = new AssembleComputer(stdConfig); 
 
stdConfig_Computer.runSystemDiagnosis(); 

When the preceding code snippet is executed, you'll get the following output:

Average Speed Processor 
Storage Operation is performed on HDD 
Display in LCD Monitor 

An updated class diagram will look like the following image. We have introduced an extra layer of factory classes to handle the creation of a processor, storage, and monitor:

A new perspective on the abstract factory pattern

In this design, we learned how multiple factories can be combined together without knowing their actual implementation to produce a final product.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset