Register  Login
OO Cobol Examples » Design Patterns » Adapter
 

Sponsors


Adapter

"Leave no interface behind!"

Adapter

Sometimes there are occasions where legacy classes that belong to old applications could be used to do part of a job (for example, credit card processing), but since they were designed for different application it could implement incompatible interfaces that would make their use a painful experience. So, what we need here is a way to adapt one interface for a class into one compatible since we want to reuse and not rewrite code that works well. What we can do? Adapter pattern to the rescue!


UML diagram

 

The diagram above depict the basic idea of a class adapter that solve the issue that arise when  "Client" class need to use similar classes that implements different interfaces. Without the adapter, the "client" would need do the following :

  • to test class name in order to call the proper method or
  • to test the payment method, thus it would need to know payment implementation details 

None of these methods above represents a good aproach since the it would require a change in the "client" class everythime a new payment method is added to the application.

To clarify our statement above lets illustrate it with a scenario:  

  1. Client class is processing a purchase order
  2. The online payment method is supposed to use existing classes for Visa and Amex from a legacy application.
  3. Visa and Amex classes uses a similar, but incompatible method to process the payment.
  4. We want to unify the way Client request the payment processing, so there would be only one method to process the payment no matter the preferred payment method (both current and any other credit card that could be adopted in the future).

   
Code

Legacy Amex class

 1:  CLASS-ID. AmexPaymentMethod AS "Legacy.AmexPaymentMethod".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  class SystemString as "System.String".
 6:                  
 7:   *> Instance's data and methods
 8:      object.
 9:         data division.
 10:              working-storage section.
 11:              copy "types.book".
 12:        
 13:         procedure division.
 14:               *> ...
 15:              
 16:               method-id. ChargeCreditCard  as "ChargeCreditCard".
 17:              
 18:               data division.
 19:                  linkage section.
 20:                      01 total              type SystemDouble.
 21:                      01 creditCardNumber   usage object reference SystemString.
 22:                      01 securityCode       usage object reference SystemString.
 23:              
 24:               procedure division using total, creditCardNumber, securityCode.
 25:              
 26:               *>  Do some connection to the credit card operator and process the payment
 27:              
 28:              end method ChargeCreditCard.    
 29:      end object.
 30:  END CLASS AmexPaymentMethod.

 
Legacy Visa class

 1:  CLASS-ID. VisaPaymentMethod AS "Legacy.VisaPaymentMethod".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  class SystemString as "System.String".
 6:                  
 7:   *> Instance's data and methods
 8:      object.
 9:          data division.
 10:              working-storage section.
 11:              copy "types.book".
 12:                  
 13:          procedure division.
 14:               *> ...
 15:              
 16:               method-id. ProcessPayment  as "ProcessPayment".
 17:              
 18:               data division.
 19:                  linkage section.
 20:                      01 securityCode       usage object reference SystemString.
 21:                      01 CardNumber         usage object reference SystemString.
 22:                      01 amount             type SystemDouble.
 23:              
 24:               procedure division using securityCode, CardNumber, amount.
 25:              
 26:               *>  Do some connection to the credit card operator and process the payment
 27:              
 28:              end method ProcessPayment.    
 29:      end object.
 30:  END CLASS VisaPaymentMethod.


The following class would return the purchase details for a given customer order. The payment method would be an instance of VisaPaymentMethod or AmexPaymentMethod classes, according to the customer selection during the order process:


Class PurchaseDetails
 

 1:  CLASS-ID. PurchaseDetails AS "AdapterPattern.PurchaseDetails".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICreditCardAdapter      as "AdapterPattern.ICreditCardAdapter"
 6:                  class SystemString                as "System.String"
 7:                  class SystemColArrayList          as "System.Collections.ArrayList"
 8:                  class ClassProduct                as "AdapterPattern.Product"
 9:                  class ClassCustomer               as "AdapterPattern.Customer"
 10:                  class ClassVisa                   as "Legacy.VisaPaymentMethod"
 11:                  class ClassAmex                   as "Legacy.AmexPaymentMethod"
 12:                  class ClassVisaWrapper            as "AdapterPattern.VisaPaymentMethodWrapper"
 13:                  class ClassAmexWrapper            as "AdapterPattern.AmexPaymentMethodWrapper"
 14:                  class SystemObject                as "System.Object".
 15:                  
 16:   *> Instance's data and methods
 17:      object.
 18:         data division.
 19:              working-storage section.
 20:              copy "types.book".
 21:              
 22:                  01 anId                 type SystemInt32 value zeros.
 23:                  
 24:              *> Properties    
 25:                  01 purchaseId           type SystemInt32                              property.
 26:                  01 totalValue           type SystemDouble                             property.
 27:                  01 aCustomer            usage object reference ClassCustomer          property.
 28:                  01 products             usage object reference SystemColArrayList     property.
 29:                  01 paymentMethod        usage object reference SystemObject           property.
 30:                  01 paymentMethodAdapter usage object reference ICreditCardAdapter     property.
 31:                  
 32:          procedure division.
 33:               *> ...
 34:              
 35:               method-id. GetDetails as "GetDetails".
 36:              
 37:               data division.
 38:                  working-storage section.
 39:                  01 anInteger      type SystemInt32  value zeros.
 40:                  01 amount         type SystemDouble value zeros.
 41:                  01 aProduct       usage object reference ClassProduct.
 42:                  
 43:                  linkage section.
 44:                      01 purchaseId type SystemInt32.
 45:              
 46:               procedure division using purchaseId.
 47:              
 48:               *>  Do some DB access here and return a purchase based on its ID 
 49:               *>  ...
 50:               *>  In our example we will return a fake purchase.    
 51:                  
 52:                   invoke ClassCustomer "NEW" returning aCustomer
 53:                  
 54:                   invoke aCustomer "GetCustomer" using 10
 55:      
 56:                   set purchaseId of self  to  purchaseId
 57:                  
 58:                   *> Lets say that even ids are going charge Amex. Odd charges Visa. 
 59:                  
 60:                   divide purchaseId by 2 giving anInteger remainder anInteger
 61:                  
 62:                   if  (anInteger = 0) then
 63:                       set paymentMethod  to  ClassAmex::"NEW"
 64:                   else
 65:                       set paymentMethod  to  ClassVisa::"NEW"
 66:                   end-if
 67:                   *> Here would come the code to retrieve the purchase from the DB
 68:                   *> We will simulate a purchase with 10 items
 69:                  
 70:                   invoke SystemColArrayList "NEW" returning products
 71:                  
 72:                   move   zeros           to  anId
 73:                  
 74:                   perform 10 times
 75:                       invoke ClassProduct "NEW" returning aProduct
 76:                       invoke aProduct "GetProduct" using anId
 77:                       invoke products "Add" using aProduct
 78:                      
 79:                       add    price of aProduct  to  amount
 80:                      
 81:                       add    1                  to  anId
 82:                   end-perform
 83:                  
 84:                   set totalValue of self        to  amount.
 85:              
 86:              end method GetDetails.    
 87:              
 88:              method-id. GetDetailsAdapted as "GetDetailsAdapted".
 89:              
 90:               data division.
 91:                  working-storage section.
 92:                  01 anInteger      type SystemInt32  value zeros.
 93:                  01 amount         type SystemDouble value zeros.
 94:                  01 aProduct       usage object reference ClassProduct.
 95:                  
 96:                  linkage section.
 97:                      01 purchaseId type SystemInt32.
 98:              
 99:               procedure division using purchaseId.
 100:              
 101:               *>  Do some DB access here and return a purchase based on its ID 
 102:               *>  ...
 103:               *>  In our example we will return a fake purchase.    
 104:                  
 105:                   invoke ClassCustomer "NEW" returning aCustomer
 106:                  
 107:                   invoke aCustomer "GetCustomer" using 1
 108:                   set purchaseId of self        to  purchaseId
 109:                  
 110:                   *> Lets say that even ids are going charge Amex. Odd charges Visa. 
 111:                  
 112:                   divide purchaseId by 2 giving anInteger remainder anInteger
 113:                  
 114:                   if  (anInteger = 0) then
 115:                       invoke ClassAmexWrapper  "NEW" returning paymentMethodAdapter
 116:                   else
 117:                       invoke ClassVisaWrapper  "NEW" returning paymentMethodAdapter
 118:                   end-if
 119:                   *> Here would come the code to retrieve the purchase from the DB
 120:                   *> We will simulate a purchase with 10 items
 121:                   invoke SystemColArrayList "NEW" returning products
 122:                  
 123:                   move   zeros           to  anId
 124:                  
 125:                   perform 10 times
 126:                       invoke ClassProduct "NEW" returning aProduct
 127:                       invoke aProduct "GetProduct" using anId
 128:                       invoke products "Add" using aProduct
 129:                      
 130:                       add    price of aProduct  to  amount
 131:                      
 132:                       add    1                  to  anId
 133:                   end-perform
 134:                  
 135:                   set totalValue of self        to  amount.
 136:              
 137:              end method GetDetailsAdapted.    
 138:      end object.
 139:  END CLASS PurchaseDetails.

OrderProcessing is the  "Client" class. It has no clue of what payment method has been chosen by the customer during the purchase process. In order to use the proper class it needs to use reflection in order to check the class name.  This is error prone, and this is exactly what we should NOT do:

 1:  CLASS-ID. OrderProcessing AS "AdapterPattern.OrderProcessing".
 2:  *> ... 
 3:  *> Instance's data and methods
 4:          procedure division.
 5:               *> ... lots of methods
 6:               method-id. ProcessOrdersLegacy  as "ProcessOrderLegacy".
 7:                   data division.
 8:                       working-storage section.
 9:                       01 aPurchase usage object reference ClassPurchaseDetails.
 10:                      
 11:                   procedure division.
 12:                      
 13:                       *> Here would be code to retrieve order from a DB
 14:                       *> ...
 15:                      
 16:                       invoke ClassPurchaseDetails "NEW" returning aPurchase
 17:                      
 18:                       invoke aPurchase "GetDetails" using purchaseNumber
 19:                      
 20:                       add    1             to  purchaseNumber
 21:                      
 22:                       set    aCustomer     to  aCustomer of aPurchase
 23:                       set creditCardType   to  paymentMethod of aPurchase
 24:                      
 25:                       *> Now we need to know which credit card the user is 
 26:                       *> using.
 27:                       *> Since both legacy Visa and Amex classes has 
 28:                       *> incompatible interfaces, a single 
 29:                       *> method call would not be enough.
 30:                      
 31:                       invoke creditCardType "GetType" returning aType
 32:                      
 33:                       *> aType now contains the object type. 
 34:                      
 35:                       invoke aType "ToString" returning creditCardName.
 36:                       if (creditCardName::"Equals"("Legacy.VisaPaymentMethod")
 37:                                                                      = b"1") then
 38:                           *> We now need to perform a cast to use the object
 39:                           set    visaLegacyCasted    to   creditCardType  as
 40:                                                           ClassLegacyVisa
 41:                           *> Some verbosity...alas
 42:                           set  creditCardNumber      to  creditCardNumber of
 43:                                                          aCustomer
 44:                           set  securityCode          to  securityCode     of
 45:                                                          aCustomer
 46:                           set  amount                to  totalValue       of
 47:                                                          aPurchase
 48:                          
 49:                           invoke visaLegacyCasted "ProcessPayment" using
 50:                                                              creditCardNumber
 51:                                                              securityCode    
 52:                                                              amount    
 53:                       end-if
 54:                      
 55:                       if (creditCardName::"Equals"("Legacy.AmexPaymentMethod")
 56:                                                                     = b"1") then
 57:                           *> Not only tedious, but also error prone
 58:                           *> Code would not work  if type name is incorrect
 59:                           *> or even abend if casted type method 
 60:                           *>name/parameters don't match...
 61:                           set amexLegacyCasted to creditCardType as
 62:                                                               ClassLegacyAmex
 63:                           set creditCardNumber to creditCardNumber of aCustomer
 64:                           set securityCode     to securityCode     of aCustomer
 65:                           set amount           to totalValue       of aPurchase
 66:                          
 67:                           invoke amexLegacyCasted "ChargeCreditCard" using
 68:                                                                amount
 69:                                                                creditCardNumber
 70:                                                                securityCode    
 71:                       end-if
 72:                       *> If another credit card is going to be supported you
 73:                       *> would need to change this code 
 74:                       *> We need a simple, unified method that could handle 
 75:                       *> any credit card, both current and future...
 76:              end method ProcessOrdersLegacy.
 77:      *> ... 

The approach described above has the following issues:

  • "Client" class OrderProcessing has to deal with implementation details of payment methods
  • This class would need to be modified in order to support new credit cards (Master, Dinners etc)
  • If the class name is not correct the code would fail
  • Since the compiler cannot validate method name nor parameter order / type, there is no guarantee that the method would work at all. 


The adapter pattern

First we need to define the adapter. It will be defined as an Interface type:

 1:  INTERFACE-ID. ICreditCardAdapter as "AdapterPattern.ICreditCardAdapter".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  class SystemString as "System.String".
 6:                  
 7:      procedure division.
 8:          method-id. ProcessPaymentAdapter as "ProcessPaymentAdapter".  
 9:              data division.
 10:                  linkage section.
 11:                      01 CardNumber         usage object reference SystemString.
 12:                      01 securityCode       usage object reference SystemString.
 13:                      01 amount             usage comp-2.
 14:              
 15:               procedure division using securityCode, CardNumber, amount.
 16:               *> Interfaces do not contain any logic, just the desired interface
 17:              
 18:          end method ProcessPaymentAdapter.
 19:          
 20:  END INTERFACE ICreditCardAdapter.


Legacy classes has to be wrapped, so the adapter would call the instance of wrapped classes:

Wrapper for Amex Credit Card:

 1:  CLASS-ID. AmexPaymentMethodWrapper AS "AdapterPattern.AmexPaymentMethodWrapper".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICreditCardAdapter       as "AdapterPattern.ICreditCardAdapter"
 6:                  class ClassLegacyAmexPaymentMethod as "Legacy.AmexPaymentMethod"
 7:                  class SystemString                 as "System.String".
 8:                  
 9:   *> Instance's data and methods
 10:      object. implements ICreditCardAdapter.
 11:         data division.
 12:              working-storage section.
 13:              copy "types.book".
 14:        
 15:         procedure division.
 16:               *> ...
 17:              
 18:               method-id. ProcessPaymentAdapted as "ProcessPaymentAdapter".  
 19:              
 20:                   data division.
 21:                       working-storage section.
 22:                       01 legacyAmexPaymentMethod usage object reference ClassLegacyAmexPaymentMethod.
 23:                      
 24:                       linkage section.
 25:                       01 CardNumber         usage object reference SystemString.
 26:                       01 securityCode       usage object reference SystemString.
 27:                       01 amount             type SystemDouble.
 28:              
 29:                   procedure division using securityCode, CardNumber, amount.
 30:              
 31:               *> Now we call the legacy method providing required parameters' order 
*> and correct method name
 32:              
 33:                      invoke ClassLegacyAmexPaymentMethod "NEW" 
returning legacyAmexPaymentMethod.
 34:              
 35:                      invoke legacyAmexPaymentMethod "ChargeCreditCard" 
                                 using amount, cardNumber, securityCode.
 36:              
 37:              end method ProcessPaymentAdapted.    
 38:      end object.
 39:  END CLASS AmexPaymentMethodWrapper.


Wrapper for Visa Credit Card:

 1:  CLASS-ID. VisaPaymentMethodWrapper AS "AdapterPattern.VisaPaymentMethodWrapper".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICreditCardAdapter       as "AdapterPattern.ICreditCardAdapter"
 6:                  class ClassLegacyVisaPaymentMethod as "Legacy.VisaPaymentMethod"
 7:                  class SystemString                 as "System.String".
 8:                  
 9:   *> Instance's data and methods
 10:      object. implements ICreditCardAdapter.
 11:         data division.
 12:              working-storage section.
 13:              copy "types.book".
 14:        
 15:         procedure division.
 16:               *> ...
 17:              
 18:               method-id. ProcessPaymentAdapter as "ProcessPaymentAdapter".  
 19:              
 20:                   data division.
 21:                       working-storage section.
 22:                       01 legacyVisaPaymentMethod usage object reference 
ClassLegacyVisaPaymentMethod.
 23:                      
 24:                       linkage section.
 25:                       01 CardNumber         usage object reference SystemString.
 26:                       01 securityCode       usage object reference SystemString.
 27:                       01 amount             type SystemDouble.
 28:              
 29:                   procedure division using securityCode, CardNumber, amount.
 30:              
 31:               *> Now we call the legacy method providing required parameters' 
*> order and correct method name
 32:              
 33:                      invoke ClassLegacyVisaPaymentMethod "NEW" 
returning legacyVisaPaymentMethod.
 34:              
 35:                      invoke legacyVisaPaymentMethod "ProcessPayment" 
                                            using  securityCode, cardNumber, amount.
 36:              
 37:              end method ProcessPaymentAdapter.    
 38:      end object.
 39:  END CLASS VisaPaymentMethodWrapper.


The class that return the payment method has to be updated in order to return the wrapper, instead of the legacy class. We can improve this using another pattern...later :)

 1:              method-id. GetDetailsAdapted as "GetDetailsAdapted".
 2:              
 3:               data division.
 4:                  working-storage section.
 5:                  01 anInteger      type SystemInt32  value zeros.
 6:                  01 amount         type SystemDouble value zeros.
 7:                  01 aProduct       usage object reference ClassProduct.
 8:                  
 9:                  linkage section.
 10:                      01 purchaseId type SystemInt32.
 11:              
 12:               procedure division using purchaseId.
 13:              
 14:               *>  Do some DB access here and return a purchase based on its ID 
 15:               *>  ...
 16:               *>  In our example we will return a fake purchase.    
 17:                  
 18:                   invoke ClassCustomer "NEW" returning aCustomer
 19:                  
 20:                   invoke aCustomer "GetCustomer" using 1
 21:                   set purchaseId of self        to  purchaseId
 22:                  
 23:                   *> Lets say that even ids are going charge Amex. Odd charges Visa. 
 24:                  
 25:                   divide purchaseId by 2 giving anInteger remainder anInteger
 26:                  
 27:                   if  (anInteger = 0) then
 28:                       invoke ClassAmexWrapper  "NEW" returning paymentMethodAdapter
 29:                   else
 30:                       invoke ClassVisaWrapper  "NEW" returning paymentMethodAdapter
 31:                   end-if
 32:                   *> Here would come the code to retrieve the purchase from the DB
 33:                   *> We will simulate a purchase with 10 items
 34:                   invoke SystemColArrayList "NEW" returning products
 35:                  
 36:                   move   zeros           to  anId
 37:                  
 38:                   perform 10 times
 39:                       invoke ClassProduct "NEW" returning aProduct
 40:                       invoke aProduct "GetProduct" using anId
 41:                       invoke products "Add" using aProduct
 42:                      
 43:                       add    price of aProduct  to  amount
 44:                      
 45:                       add    1                  to  anId
 46:                   end-perform
 47:                  
 48:                   set totalValue of self        to  amount.
 49:              
 50:              end method GetDetailsAdapted.    

            
Finally, the OrderProcessing class will be changed to support the adapter. All hard work is passed into the adapter that ensure that the correct method is called, and the right parameters are passed, always.

 1:              *>Enter the Adapter Pattern!
 2:              method-id. ProcessOrdersWithAdapter  as "ProcessOrdersWithAdapter".
 3:                   data division.
 4:                       working-storage section.
 5:                       01 aPurchase usage object reference ClassPurchaseDetails.
 6:                      
 7:                   procedure division.
 8:                      
 9:                       *> Here would be code to retrieve order from a DB
 10:                       *> ...
 11:                      
 12:                       invoke ClassPurchaseDetails "NEW" returning aPurchase
 13:                        invoke aPurchase "GetDetailsAdapted" using 1
 14:                       set  aCustomer                 to  aCustomer of aPurchase
 15:                      
 16:                       set  creditCardNumber    to  creditCardNumber     of aCustomer
 17:                       set  securityCode        to  securityCode         of aCustomer
 18:                       set  amount              to  totalValue           of aPurchase
 19:                      
 20:                       set  creditCardAdapter   to  paymentMethodAdapter of aPurchase  
 21:                      
 22:                       invoke creditCardAdapter "ProcessPaymentAdapter" using 
creditCardNumber
 23:                                                                    securityCode    
 24:                                                                    amount    
 25:                                                                      
 26:                       *> What a relief! Code is cleaner, understandable and smaller!
 27:              end method ProcessOrdersWithAdapter.

Notice that the new method ProcessOrdersWithAdapter of OrderProcessing no longer care about payment method implementation details. The best part is that if a new credit card is going to be supported, no changes are required here anymore.

The adapter pattern also allows the compiler to check the conformance, improving application robustness.
 

 
 

Download the source

http://cobolrocks.codeplex.com/SourceControl/changeset/view/37478#578503


Some references in the web

http://www.dofactory.com/Patterns/PatternAdapter.aspx
http://en.wikipedia.org/wiki/Adapter_pattern

 
 
 
 
Creative Commons License The text of this site is licensed under a Creative Commons License.

 

 

Comments

 Name  
 Email
 Comment  
CAPTCHA image
Enter the code shown above

Terms Of Use | Privacy Statement | Copyright 2009-2010 by RedRailsDynnamite DotNetNuke Skins & Modules