Chapter 3.  Structural Patterns

In the previous chapter, we have seen that creational patterns can help us create objects with a simplified and extensible approach. Moving forward, as we develop various classes, our next challenge is to integrate these classes. In some scenarios, where we need to establish a relationship between two classes and they do not know each other's behavior or are incompatible with each other or are frequently changed, it is very important to establish a robust relationship between these classes. Structural design patterns help us design a robust relationship between multiple classes.

Structural patterns can be used in the following scenarios:

  • Dynamically assigning an additional behavior to the existing object 
  • Creating a relationship between incompatible classes
  • Simplifying an interaction with complex classes
  • Reusing an object to improve the memory footprint

In this chapter, we will discuss various ways to create an optimum relationship between multiple classes.

We will go through the following structural design patterns:

  • The facade pattern
  • The adapter pattern
  • The bridge pattern
  • The composite pattern
  • The decorator pattern
  • The flyweight pattern

The facade pattern

By the time an application is being built, the chances are high that the existing classes become more complex and require complex client code for interaction. Often, this complex code for interaction is duplicated wherever a relationship is to be established. Also, a developer needs to be aware of all the aspects of this complex interaction code, such as a sequence in which it should be executed. The façade pattern helps us hide such complex interaction code and makes it reusable.

Note

The intention of the façade design pattern is to convert complex Apex classes into simplified classes or interfaces where groups of classes are either wrapped into one class or only one class is used to delegate responsibilities to the other classes.

Most prominent use cases of the façade design pattern include scenarios where a client needs to have multiple interactions to achieve a unit of work or the interaction code is duplicated at multiple locations.

Consider the example of an online shopping store. Whenever any order is placed in such an application, a series of complicated steps need to be executed before a product is shipped to the client address. It needs to go through steps, such as updating the inventory, verifying the address, calculating a discount, verifying payment, and shipping the product. All these steps are complex and can be considered modules. Also, the sequence of these steps is equally important.

Let's take a look at the following code for all the previously mentioned modules of an online store application. Below each Apex class, you will see the steps performed before a product is shipped to the customer:

/*
Class to contain methods related to operations on Inventory
*/
public class Inventory { 
    
  //This method updates Inventory 
  public boolean updateInventory(String prodId, Integer count) 
  { 
    System.debug(count+' Product with Id '+prodId+' is subtracted from Inventory'); 
    return true; 
  } 
     
} 

/*
Class to contain methods related to operations on Address Verification
*/
public class AddressVerification { 
    
  //This method is used to verify address 
  public boolean verify(String zipAdd){ 
    System.debug('Product can be shipped at zip - '+zipAdd); 
    return true; 
  } 
} 

/*
Class to contain methods related to operations for applying discount
*/
public class ApplyDiscount { 
 
  //return discounted price 
  public decimal calculate(Decimal actualPrice,Decimal discount){  
    Decimal finalPrice = actualPrice - (discount/100 *actualPrice) ; 
    System.debug('Final price, After '+  discount/100 +'% discount applied is '+finalPrice); 
    return finalPrice; 
  }  
} 

public class PaymentVerification { 
    
    public boolean verify(String cardNumber){ 
      System.debug('Card with number '+cardNumber+' is used for payment'); 
      return true; 
    }  
} 

/*
Class to contain methods related to operations for shipping
*/
public class ShipToAddress { 
     
  //Ship product to address 
  public boolean ship(String add, String prodName){ 
    System.debug('"'+prodName+'" is shipped to '+add); 
    return true; 
  } 
} 

In a real-world application, preceding classes can be huge and can involve numerous transactions. For the sake of simplicity, only a skeleton of classes has been provided in this example.

We need one more class to hold all the information about the product ordered by a customer:

public class OrderDetail { 
  //public properties 
  public String productId {get;set;} 
  public String productName {get;set;} 
  public Integer productCount  {get;set;} 
  public String zipCode {get;set;} 
  public Decimal price {get;set;} 
  public Decimal discount {get;set;} 
  public String paymentCardNumber {get;set;} 
  public String address {get;set;} 
     
  //Constructor 
  public OrderDetail(String productId, String productName, Integer  
  productCount, String zipCode, Decimal price, Decimal discount,   
  String paymentCardNumber, String address ){ 
    this.productId = productId; 
    this.productName = productName; 
    this.productCount = productCount; 
    this.zipCode = zipCode; 
    this.price = price; 
    this.discount = discount; 
    this.paymentCardNumber = paymentCardNumber; 
    this.address = address;  
  } 
} 

Now, to place an order, the following code needs to be used at all the required locations:

OrderDetail order = new OrderDetail('IBN-abcd123','Apex Design Pattern', 1, '06042',40,15,'123456789098754','Manchester Buckland hills'); 
 
Inventory inv = new Inventory(); 
inv.updateInventory(order.productId , order.productCount ); 
 
AddressVerification add = new AddressVerification(); 
add.verify(order.zipCode); 
 
ApplyDiscount disc = new ApplyDiscount(); 
disc.calculate(order.price , order.discount); 
 
PaymentVerification pym = new PaymentVerification(); 
pym.verify(order.paymentCardNumber); 
 
ShipToAddress shipProduct = new ShipToAddress(); 
shipProduct.ship(order.address , order.productName); 

The output of the preceding code is as follows:

1 Product with Id IBN-abcd123 is subtracted from Inventory 
Product can be shipped at zip - 06042 
Final price, After 0.15% discount applied is 34.00 
Card with number 123456789098754 is used for payment 
"Apex Design Pattern" is shipped to Manchester Buckland hills 

The following figure shows a high-level diagram of the current state of an application:

The facade pattern

In the preceding example, a problem is very evident. Every piece of client code has to ensure that all the required steps are executed and as per the specified sequence. Any changes made to the steps involved or to the sequence of these steps will result in a ripple effect on all code locations wherever they have been used.

Also, in the preceding figure, the client code is tightly coupled with multiple subsystems, such as card verification, inventory update, applying discount, and so on.

As all the subsystems are well tested and dependent on many other modules, changing the code would not be a wise decision. As there are many large classes, and the client uses the repetitive code for interaction, it strongly hints at the usage of the façade pattern.

While implementing façade pattern, we do not make any changes to the existing code, but we create a new class or layer, which takes care of the interaction with subsystems.

The following Apex class represents a newly written façade class:

public class OnlineStoreFacade { 
              
  //This method takes care of processing all steps in sub systems 
  public void processSteps(OrderDetail order){  
 
    Inventory inv = new Inventory(); 
    AddressVerification add = new AddressVerification(); 
    ApplyDiscount disc = new ApplyDiscount(); 
    PaymentVerification pym = new PaymentVerification(); 
    ShipToAddress shipProduct = new ShipToAddress(); 
 
    inv.updateInventory(order.productId , order.productCount ); 
    add.verify(order.zipCode); 
    disc.calculate(order.price , order.discount); 
    pym.verify(order.paymentCardNumber); 
    shipProduct.ship(order.address , order.productName); 
  } 
} 

The client needs to use only the following three lines of code for the interaction:

OrderDetail order = new OrderDetail('IBN-abcd123','Apex Design Pattern', 1, '06042',40,15,'123456789098754','Manchester Buckland hills'); 
OnlineStoreFacade facade = new OnlineStoreFacade(); 
facade.processSteps(order); 

The following class diagram shows the façade pattern implemented in this example:

The facade pattern

Other use cases

Let's say that we have generated Apex classes from a web service's WSDL file using the WSDLtoApex tool. Each time before invoking a web service, we may need to set up the endpoint URL or timeout or any other parameter. This leads to code redundancy. In these scenarios as well, we can have a common Apex class, which can be used to invoke web services, and all these repetitive settings can be part of this Apex class.

Note

WSDLtoApex is an open source tool and part of the Force.com IDE plugin for Eclipse. You can find this tool, or if you want to contribute to this tool, you can visit its GitHub repository page at https://github.com/forcedotcom/WSDL2Apex.

There is one more tool available at FuseIT, which goes one level higher by allowing you to generate the Apex class only for the required method from WSDL. This tool is very helpful in scenarios where WSDL generates hundreds of classes and consumes the allowed Apex limit for the Salesforce instance. You can find more information about this tool at http://www.fuseit.com/Solutions/SFDC-Explorer.aspx.

Points to consider

  • The façade pattern emphasizes on ensuring compatibility with the existing implementations by not modifying the existing classes
  • It is widely used in API development to provide easier client interaction
..................Content has been hidden....................

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