Register  Login
OO Cobol Examples » Design Patterns » Abstract factory
 

Abstract factory

"Classes...lots of classes!"

Abstract factory

Lets imagine that you have a POS (Point Of Sale) application that supports hardware from a well known supplier. Your most important customer decided to use a different supplier and asked you to adapt your application to use it. You realize that you may need to add support to a third and even a fourth supplier in the future, but you don't want to increase the complexity of your application. What you need is to raise the abstraction bar, isolating the business logic from classes that deal with POS hardware such as keyboards, screens, card readers, receipt printers, barcode scanners etc. A very good solution would be to get rid of "NEW" operator so your application would not be so tighly coupled with any specific class that deals with hardware! How? Meet the Abstract Factory!


UML diagram

This is the Abstract Factory classic definition: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.


Don't let this intimidate you. Abstract Factories are not only simple, but pretty flexible solutions that comes in handy very often. Once you master that concept you will not help yourself imagining new and creative uses for it.


The checkout

Checkouts usually comprises:

  • computer
  • screen
  • keyboard
  • barcode scanner
  • receipt printer
  • pin pad with Integrated Card Swipe
  • cash drawer
  • credit card reader

Our application should support many preconfigured checkouts, so we would be able to replace a checkout brand with another one without any impact in the business rules. The key here is to keep  business logic as simples as possible and completely unaware of which device brand is being used, as long is provides the expected services.

First let see what we are trying to avoid:

  1: CLASS-ID. ProcessOrder AS "NotSoCoolThings.ProcessOrder".
  2:  
  3:       *> ...
  4:  
  5:     method-id. ProcessNewOrder as "ProcessNewOrder".
  6:       *> ...
  7:         procedure division.
  8:         *> ...
  9:         *> creating checkout support classes
 10:         *> (THAT'S EXACTLY WHAT WE DO NOT WANT TO DO!)
 11: 
 12:        if checkoutType = "HP"
 13:            invoke HPFK182AT "NEW" returning cashDrawerHP
 14:            invoke HPFK224AT "NEW" returning receiptPrinterHP
 15:        else
 16:            invoke TeamPos1054258002 "NEW" returning cashDrawerFJ
 17:            invoke TeamPoSFD21 "NEW" returning receiptPrinterFJ
 18:        end-if
 19:  
 20:         *> Loop getting items and printing
 21:         
 22:           *> ...
 23:           *> Just imagine how would be to have another hardware supplier
 24:           if chechoutType = "HP"
 25:  
 26:             invoke receiptPrinterHP "printItem" using item, quantity
 27:           else
 28:             invoke receiptPrinterFJ "printItem" using item, quantity
 29:           end-if  
 30: 
 31:           *> What if you could mix parts?...there would be endless combinations!
 32:  
 33:           if chechoutType = "HP"
 34:  
 35:             invoke cashDrawerHP "OpenDrawer"
 36:           else
 37:             invoke cashDrawerFJ "OpenDrawer"
 38:           end-if  
 39: 
 40:           *> ...
 41:           *> There must be a better way to do this!
 42:  
 43:  
 44:       end method ProcessNewOrder.
 45:   end object.
 46: END CLASS ProcessOrder.
 47:  

 The code above clearly shows the high dependency of classes that deal with checkout parts. If we add support to a different hardware supplier, our code would suffer with lots of "ifs" to check which equipament is in use. You could make your life easier by forcing those classes to implement a common interface, but still you would need to code a lot of methods to test the interaction between parts and class creation and all that code would go along business rules...creepy!


These will be our checkout systems:

Fujitsu
. Cash drawer - model TeamPos1054258002
. Receipt printer - model TeamPoSFD21

HP
. Cash drawer - model HPFK182AT
. Receipt printer - model HPFK224AT

In addition to get rid of high dependency (and the mess in the business logic) we would like also to define which set of parts our checkout is made of. We are going to use an XML file to define checkout configuration and which one our application is going to use. 

APP.config (xml file)

 1:  ?xml version="1.0" encoding="utf-8" ?
 2:  configuration
 3:    checkout
 4:      currentSupplier/configuration/checkout/suppliers/fujitsu/currentSupplier
 5:      suppliers
 6:        fujitsu
 7:          currentSet/configuration/checkout/suppliers/fujitsu/sets/TeamPos500/currentSet
 8:          sets
 9:            TeamPos500
 10:              cashDrawerTeamPos1054258002/cashDrawer
 11:              receiptPrinterTeamPoSFD21/receiptPrinter
 12:            /TeamPos500
 13:            TeamPos1000
 14:              cashDrawerTeamPos10PB60014/cashDrawer
 15:              receiptPrinterTeamPoSFD22/receiptPrinter
 16:            /TeamPos1000
 17:          /sets
 18:        /fujitsu
 19:        hp
 20:          currentSet/configuration/checkout/suppliers/hp/sets/HPPOS4SmallBusiness/currentSet
 21:          sets
 22:            HPPOS4SmallBusiness
 23:              cashDrawerHPFK182AT/cashDrawer
 24:              receiptPrinterHPFK224AT/receiptPrinter
 25:            /HPPOS4SmallBusiness
 26:          /sets
 27:        /hp
 28:      /suppliers
 29:    /checkout
 30:    db
 31:      connection
 32:        oracle
 33:          DataSourceData Source=TORCL/DataSource
 34:          UserIdadmin/UserId
 35:          PasswordmyPassword/Password
 36:        /oracle
 37:      /connection
 38:    /db
 39:  /configuration

The XML currently defines two suppliers: Fujitsu and HP. Each supplier could have multiple sets, but only one active. A global setting defines what would be the current supplier used by the app.

 

The abstract factory

The main interface of this design pattern is the abstract factory interface. This is the interface that will be used by the client app to create concrete factories. Lets call it ICheckoutAbstractFactory:
 
 1:  INTERFACE-ID. ICheckoutAbstractFactory as "AFP.ICheckoutAbstractFactory".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICashDrawer     as "AFP.ICashDrawerAbstractProduct"
 6:                  interface IReceiptPrinter as "AFP.IReceiptPrinterAbstractProduct".
 7:                  *> here would enter any other abstract product that is part of a checkout.
 8:                  
 9:      procedure division.
 10:          method-id. CreateCashDrawer as "CreateCashDrawer".  
 11:              data division.
 12:                  linkage section.
 13:                      01 cashDrawer usage object reference ICashDrawer.
 14:              
 15:              procedure division returning cashDrawer.
 16:            *> Interfaces do not contain any logic, just the desired interface
 17:              
 18:          end method CreateCashDrawer.
 19:          method-id. CreateReceiptPrinter as "CreateReceiptPrinter".  
 20:              data division.
 21:                  linkage section.
 22:                      01 receiptPrinter usage object reference IReceiptPrinter.
 23:              
 24:              procedure division returning receiptPrinter.
 25:              *> Interfaces do not contain any logic, just the desired interface
 26:              
 27:          end method CreateReceiptPrinter.
 28:          
 29:  END INTERFACE ICheckoutAbstractFactory.
 

The concrete factory

A concrete factory implements the abstract factory "create" methods. It will be the only point in the code that we need to update after add a new checkout system. This is a very specialized class which only responsibility is to create the proper parts. Our application has two concrete factories: FujitsuConcreteFactory and HPConcrete Factory. If you analyse the code carefully you maybe realize that we could implement only one factory ;)

 1:  CLASS-ID. FujitsuConcreteFactory as "AFP.FujitsuConcreteFactory".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICheckoutAbstractFactory       as "AFP.ICheckoutAbstractFactory"
 6:                  interface ICashDrawerAbstractProduct     as "AFP.ICashDrawerAbstractProduct"
 7:                  interface IReceiptPrinterAbstractProduct as "AFP.IReceiptPrinterAbstractProduct"
 8:                  
 9:                  *>Cash drawers classes
 10:                  class     ClassTeamPos1054258002         as "AFP.Drivers.CashDrawers.TeamPoS105428002"
 11:                  class     ClassTeamPos10PB60014          as "AFP.Drivers.CashDrawers.TeamPoS0PB60014"
 12:                  
 13:                  *>Receipt printers classes
 14:                  class     ClassTeamPOSFD21               as "AFP.Drivers.ReceiptPrinters.TeamPOSFD21"
 15:                  class     ClassTeamPOSFD22               as "AFP.Drivers.ReceiptPrinters.TeamPOSFD22"
 16:                  
 17:                  class     ClassUtils                     as "AFP.Utils"
 18:                  class     CollectionHashTable            as "System.Collections.Hashtable"
 19:                  class   �SystemString                   as "System.String"
 20:                  class  �SystemObject                   as "System.Object".
 21:      object. implements ICheckoutAbstractFactory.              
 22:          data division.
 23:              working-storage section.
 24:                  01 checkoutHardware    usage object reference SystemString.
 25:                  01 receiptPrinterName  pic n(80) value spaces.
 26:                  01 cashDrawerName      pic n(80) value spaces.
 27:                  01 driver              usage object reference SystemObject.
 28:                  01 drivers          �usage object reference CollectionHashTable.            
 29:                  
 30:          procedure division.
 31:              method-id. NEW.
 32:                  procedure division.
 33:                      *> Getting all drivers at once
 34:                      invoke ClassUtils "GetCheckoutDrivers" returning drivers.
 35:                      
 36:              end method NEW.
 37:              
 38:              method-id. CreateCashDrawer as "CreateCashDrawer".  
 39:                  data division.
 40:                      linkage section.
 41:                          01 cashDrawer usage object reference ICashDrawerAbstractProduct.
 42:              
 43:                  procedure division returning cashDrawer.
 44:                  
 45:                      *> Get cashDrawer and receipt drivers
 46:                      
 47:                      set   driver            to  drivers::"get_Item"("CashDrawer")
 48:                      set   cashDrawerName    to  driver as SystemString
 49:                      
 50:                      *> creating the cash drawer class defined in the configuration file
 51:                      evaluate cashDrawerName
 52:                               when n"TeamPos1054258002"
 53:                                    invoke ClassTeamPos1054258002 "NEW" returning cashDrawer
 54:                               when n"TeamPos10PB60014"
 55:                                    invoke ClassTeamPos10PB60014 "NEW" returning cashDrawer
 56:                      end-evaluate
 57:              
 58:              end method CreateCashDrawer.
 59:              method-id. CreateReceiptPrinter as "CreateReceiptPrinter".  
 60:                  data division.
 61:                      linkage section.
 62:                          01 receiptPrinter usage object reference IReceiptPrinterAbstractProduct.
 63:              
 64:                  procedure division returning receiptPrinter.
 65:                      *> Get cashDrawer and receipt drivers
 66:                      
 67:                      set   driver              to  drivers::"get_Item"("ReceiptPrinter")
 68:                      set   receiptPrinterName  to  driver as SystemString
 69:                      
 70:                      *> creating the receipt printer class defined in the configuration file
 71:                      
 72:                      evaluate receiptPrinterName
 73:                               when n"TeamPoSFD21"
 74:                                    invoke ClassTeamPoSFD21 "NEW" returning receiptPrinter
 75:                               when n"TeamPoSFD22"
 76:                                    invoke ClassTeamPoSFD22 "NEW" returning receiptPrinter
 77:                      end-evaluate
 78:              
 79:              end method CreateReceiptPrinter.
 80:      end object.                
 81:  END CLASS FujitsuConcreteFactory.


The abstract product

An abstract product is the interface that allow the concrete factory to load the proper product. Without that interface, the concrete factory would be useless (or much more complex than the necessary). Below the interface for Receipt Printers:

 1:  INTERFACE-ID. IReceiptPrinterAbstractProduct as "AFP.IReceiptPrinterAbstractProduct".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICashDrawerAbstractProduct     as "AFP.ICashDrawerAbstractProduct"
 6:                  
 7:                  class SystemString   as "System.String"
 8:                  class ClassUser      as "AFP.User"      
 9:                  class ClassCompany   as "AFP.Company"
 10:                  class ClassProduct   as "AFP.Product".  
 11:                  
 12:      procedure division.
 13:          method-id. StartNewSale as "StartNewSale".
 14:              data division.
 15:                  linkage section.
 16:                      01 company  �usage object reference ClassCompany.
 17:                      01 user        usage object reference ClassUser.        
 18:              
 19:              procedure division using by value company, by value user.
 20:              
 21:          end method StartNewSale.
 22:          
 23:          method-id. RegisterItem as "RegisterItem".  
 24:              data division.
 25:                  linkage section.
 26:                      01 product�usage object reference ClassProduct.
 27:                      01 quantity  usage binary-long signed.
 28:              
 29:              procedure division using by value product, by value quantity.
 30:              
 31:          end method RegisterItem.
 32:          method-id. PrintTotal as "PrintTotal".
 33:              data division.
 34:                  linkage section.
 35:                      01 cashDrawer  usage object reference ICashDrawerAbstractProduct.
 36:                      
 37:              procedure division using by value cashDrawer.
 38:              
 39:          end method PrintTotal.
 40:          
 41:          *> ... and any other interfaces that you may need to implement
 42:          *> (e.g. CancelItem, CancelTransaction, PrintDiscount etc
 43:          
 44:  END INTERFACE IReceiptPrinterAbstractProduct.


The concrete product

A concrete product in our checkout architecture take care of all communications with the hardware, so we are calling them drivers in this context. Here is the a concrete product for a Fujitsu Cash Drawer:

 1:  CLASS-ID. TeamPoS0PB60014 as "AFP.Drivers.CashDrawers.TeamPoS0PB60014".
 2:      environment division.
 3:          configuration section.
 4:              repository.
 5:                  interface ICashDrawerAbstractProduct as "AFP.ICashDrawerAbstractProduct".
 6:      object. implements ICashDrawerAbstractProduct.              
 7:                  
 8:          procedure division.
 9:          method-id. OpenDrawer as "OpenDrawer".  
 10:              
 11:              procedure division.
 12:              
 13:                  display "Fujitsu cash drawer TeamPoS0PB60014 opened..."
 14:                  
 15:              *> here would go specific cash drawer API calls...
 16:              
 17:          end method OpenDrawer.
 18:          
 19:      end object.                
 20:  END CLASS TeamPoS0PB60014.


Using our factory

So, here is our abstract factory in action. You will notice that we are no longer directly instantiating the driver´s classes. Actually, that code has no idea which driver has been choosen. This has been delegated to the concrete factory. One very insteresting aspect is that our checkout parts can talk each other (for instance, when closing a sale, the receipt printer would open the cash drawer automatically).

  1: CLASS-ID. ProcessOrder AS "NowYouAreTalking.ProcessOrder".
  2:            *> ...
  3: method-id. ProcessNewOrder as "ProcessNewOrder".
  4:            *> ...
  5:  procedure division.
  6: 
  7:            *> ...
  8:            *> Creating a concrete factory
  9:             invoke ClassFujitsuConcreteFactory "NEW" returning checkout
 10: 
 11:             *> Creating cash drawer and receipt printer classes
 12:             invoke checkout "CreateCashDrawer"returning cashDrawer
 13:             invoke checkout "CreateReceiptPrinter" returning receiptPrinter
 14:             *> ...
 15: 
 16:             *> Using receipt printer
 17:             invoke receiptPrinter "StartNewSale" using aCompany, aUser
 18:             invoke receiptPrinter "RegisterItem" using aProduct, 10
 19: 
 20:             *> close order and open the cashDrawer
 21:             invoke receiptPrinter "PrintTotal" using cashDrawer
 22:  end method ProcessNewOrder.
 23: end object.
 24: END CLASS ProcessOrder.

 

Abstract Factories are eveywhere

ATMs, RTS games, DB connections, web frameworks and many more applications uses that useful pattern. Some examples:

Spring.Net (Web Framework)
.Net ADO.Net
Microsoft COM (yes, even the COM implements that pattern)

 
Download the source

http://cobolrocks.codeplex.com/SourceControl/changeset/view/40357#


Some references in the web

http://www.dofactory.com/Patterns/PatternAbstract.aspx
http://en.wikipedia.org/wiki/Abstract_factory_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

Sponsors


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