"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:
- Client class is processing a purchase order
- The online payment method is supposed to use existing classes for Visa and Amex from a legacy application.
- Visa and Amex classes uses a similar, but incompatible method to process the payment.
- 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