Citación del post de Eslopes
|
❞
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!
|
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:
Código COBOL:
CLASS-ID. ProcessOrder AS "NotSoCoolThings.ProcessOrder".
*> ...
method-id. ProcessNewOrder as "ProcessNewOrder".
*> ...
procedure division.
*> ...
*> creating checkout support classes
*> (THAT'S EXACTLY WHAT WE DO NOT WANT TO DO!)
if checkoutType = "HP"
invoke HPFK182AT "NEW" returning cashDrawerHP
invoke HPFK224AT "NEW" returning receiptPrinterHP
else
invoke TeamPos1054258002 "NEW" returning cashDrawerFJ
invoke TeamPoSFD21 "NEW" returning receiptPrinterFJ
end-if
*> Loop getting items and printing
*> ...
*> Just imagine how would be to have another hardware supplier
if chechoutType = "HP"
invoke receiptPrinterHP "printItem" using item, quantity
else
invoke receiptPrinterFJ "printItem" using item, quantity
end-if
*> What if you could mix parts?...there would be endless combinations!
if chechoutType = "HP"
invoke cashDrawerHP "OpenDrawer"
else
invoke cashDrawerFJ "OpenDrawer"
end-if
*> ...
*> There must be a better way to do this!
end method ProcessNewOrder.
end object.
END CLASS ProcessOrder.
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)
Código XML:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<checkout>
<currentSupplier>/configuration/checkout/suppliers/fujitsu</currentSupplier>
<suppliers>
<fujitsu>
<currentSet>/configuration/checkout/suppliers/fujitsu/sets/TeamPos500</currentSet>
<sets>
<TeamPos500>
<cashDrawer>TeamPos1054258002</cashDrawer>
<receiptPrinter>TeamPoSFD21</receiptPrinter>
</TeamPos500>
<TeamPos1000>
<cashDrawer>TeamPos10PB60014</cashDrawer>
<receiptPrinter>TeamPoSFD22</receiptPrinter>
</TeamPos1000>
</sets>
</fujitsu>
<hp>
<currentSet>/configuration/checkout/suppliers/hp/sets/HPPOS4SmallBusiness</currentSet>
<sets>
<HPPOS4SmallBusiness>
<cashDrawer>HPFK182AT</cashDrawer>
<receiptPrinter>HPFK224AT</receiptPrinter>
</HPPOS4SmallBusiness>
</sets>
</hp>
</suppliers>
</checkout>
<db>
<connection>
<oracle>
<DataSource>Data Source=TORCL</DataSource>
<UserId>admin</UserId>
<Password>myPassword</Password>
</oracle>
</connection>
</db>
</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:
Código COBOL:
INTERFACE-ID. ICheckoutAbstractFactory as "AFP.ICheckoutAbstractFactory".
environment division.
configuration section.
repository.
interface ICashDrawer as "AFP.ICashDrawerAbstractProduct"
interface IReceiptPrinter as "AFP.IReceiptPrinterAbstractProduct".
*> here would enter any other abstract product that is part of a checkout.
procedure division.
method-id. CreateCashDrawer as "CreateCashDrawer".
data division.
linkage section.
01 cashDrawer usage object reference ICashDrawer.
procedure division returning cashDrawer.
*> Interfaces do not contain any logic, just the desired interface
end method CreateCashDrawer.
method-id. CreateReceiptPrinter as "CreateReceiptPrinter".
data division.
linkage section.
01 receiptPrinter usage object reference IReceiptPrinter.
procedure division returning receiptPrinter.
*> Interfaces do not contain any logic, just the desired interface
end method CreateReceiptPrinter.
END INTERFACE ICheckoutAbstractFactory.