Blog Read

My First attempted at the "The Adapter Pattern/Wrapper Pattern"

A friend of mine turned me on to a new pattern that I think I will be using more in the future "The Adapter Pattern/Wrapper Pattern".

Intent

  • Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
  • Wrap an existing class with a new interface.
  • Impedance match an old component to a new system

--Source http://sourcemaking.com/design_patterns/adapter

So to play around with this concept, I had just shared with him payment gateway service. The function I was working on was a function called getGateway(), and it looked like this

view plain print about
1<cfargument name="MerchantProps" required="true" type="struct" >
2        <cfscript>
3            var gw = {};
4            
5            switch ( arguments.MerchantProps.vendor )
6            {
7                case 'itransact':
8                {
9                    gw.path             = "itransact.itransact_cc";
10                    gw.MerchantAccount     = arguments.MerchantProps.MerchantAccount;
11                    gw.Username         = arguments.MerchantProps.Username;
12                    gw.Password         = arguments.MerchantProps.Password;
13                    break;
14                }
15                
16                case 'authorize':
17                {
18                    gw.path             = "authorizenet.authorizenet";                    
19                    gw.MerchantAccount     = trim( arguments.MerchantProps.MerchantAccount );
20                    gw.Username         = trim( arguments.MerchantProps.Username);
21                    break;
22                }
23                
24                case 'payflowpro':
25                {
26                    gw.path         = "payflowpro.payflowpro";
27                    gw.GatewayID    = 1;//?????Not sure what this is for..Or if needed.
28
                    gw.partner         = arguments.MerchantProps.partner;
29                    gw.vendor         = arguments.MerchantProps.payflowproVendor;
30                    gw.username     = arguments.MerchantProps.Username;
31                    
32                    
33                    gw.password     = arguments.MerchantProps.Password;
34                    
35                    break;
36                }
37            }
38            
39            gw.TestMode         = arguments.MerchantProps.isInTestMode;
40            // create gw and get reference            
41
            getCFPayment().init(gw);
42            
43            gw = getCFPayment().getGateway();
44            
45            
46            return gw;
47        
</cfscript>

He asked if I had heard of the adapter pattern. When I told him no, he went into the technical details. We decided to dive into it and build a couple adapter classes, to trim down the function above. The first thing we did was create a base Adapter

view plain print about
1<cfcomponent displayname="Base Adapter" extends="lib.com.class.Class">
2    <cffunction name="process" output="false" returntype="Any">
3    
4        <cfthrow detail="This is an abstract method. It must be overwritten."/>
5    </cffunction>
6    
7</cfcomponent>

Don't worry about what the base adapter extends "lib.com.class.Class" that is just a base class that I like all of my classes to extend.You will notice that the Base Adapter has a function called process, that function is not meant to be called directly but implemented.  Next we created a Base Payment Adapter

view plain print about
1<cfcomponent displayname="Payment Adapter" extends="lib.com.class.adapter.Apdapter">
2</cfcomponent>

This class extends the Base Adapter but doesn't do anything, that does not mean that it can't do anything, at this time it just doesn't.  We then created a PayflowPro Adapter

view plain print about
1<cfcomponent displayname="PayFlowpro Adapter" extends="lib.com.class.adapter.payment.payment">
2    <cffunction name="process" returntype="void" output="false">
3        <cfargument name="data"             required="true" type="any">
4        <cfargument name="MerchantProps"     required="true" type="any">
5        <cfscript>
6            argumnets.data.path         = "payflowpro.payflowpro";
7            argumnets.data.GatewayID    = 1;//?????Not sure what this is for..Or if needed.
8
            argumnets.data.partner         = arguments.MerchantProps.partner;
9            argumnets.data.vendor         = arguments.MerchantProps.payflowproVendor;
10            argumnets.data.username     = arguments.MerchantProps.Username;            
11            argumnets.data.password     = arguments.MerchantProps.Password;
12        
</cfscript>        
13    </cffunction>
14    
15</cfcomponent>

You will notice that it does exactly what the case statement does in the function above. That's correct. And you might think, man this is a lot code for nothing. What if you didn't use that the adapter here and you left the case statement alone. You would probably be okay if all of the clients in your application used PayFlowPro the same way. If you had a client that needed to add xyz to the PayFlowPro process, you might do something like this.

view plain print about
1case 'payflowpro_xyz':
2{
3    gw.path         = "payflowpro.payflowpro";
4    gw.GatewayID    = 1;//?????Not sure what this is for..Or if needed.
5    gw.partner         = arguments.MerchantProps.partner;
6    gw.vendor         = arguments.MerchantProps.payflowproVendor;
7    gw.username     = arguments.MerchantProps.Username;
8    gw.xyz            = "this is for my new client";
9    
10    gw.password     = arguments.MerchantProps.Password;
11    
12    break;
13}

That works! But now you have this long switch statement which personally I find it hard to sift through when debugging code. With the way that I have implemented the Adapter Pattern, you would do it something like this.

view plain print about
1<cfcomponent displayname="PayFlowpro Adapter" extends="lib.com.class.adapter.payment.payment">
2    <cffunction name="process" returntype="void" output="false">
3        <cfargument name="data"             required="true" type="any">
4        <cfargument name="MerchantProps"     required="true" type="any">
5        <cfscript>
6            argumnets.data.path         = "payflowpro.payflowpro";
7            argumnets.data.GatewayID    = 1;//?????Not sure what this is for..Or if needed.
8
            argumnets.data.partner         = arguments.MerchantProps.partner;
9            argumnets.data.vendor         = arguments.MerchantProps.payflowproVendor;
10            argumnets.data.username     = arguments.MerchantProps.Username;            
11            argumnets.data.password     = arguments.MerchantProps.Password;
12            argumnets.data.xyz            = "this is for my new client";
13        
</cfscript>        
14    </cffunction>
15    
16</cfcomponent>

Now to show you the new an improved getGateway() function

view plain print about
1<cffunction name="getGateway" access="private" output="false" returntype="any">
2        <cfargument name="MerchantProps" required="true" type="struct" >
3        <cfscript>
4            var gateway_props = {};
5            
6            getAdapter( 'payment.' &amp; arguments.MerchantProps.vendor ).process( gateway_props, MerchantProps );
7            
8            gateway_props.TestMode         = arguments.MerchantProps.isInTestMode;
9            // create gateway_props and get reference            
10
            getCFPayment().init( gateway_props );
11            
12            gateway_props = getCFPayment().getGateway();
13            
14            return gateway_props;
15        
</cfscript>
16    </cffunction>

blog comments powered by Disqus