Chapter 26
GoF Design Patterns
The Adapter Design Pattern
TaxMasterAdapter
getTaxes( Sale ) : List of TaxLineItems
GoodAsGoldTaxProAdapter
getTaxes( Sale ) : List of TaxLineItems
«interface»ITaxCalculatorAdapter
getTaxes( Sale ) : List of TaxLineItems
Adapters use interfaces and polymorphism to add a level of indirection to varying APIs in other components.
SAPAccountingAdapter
postReceivable( CreditPayment )postSale( Sale )...
GreatNorthernAccountingAdapter
postReceivable( CreditPayment )postSale( Sale )...
«interface»IAccountingAdapter
postReceivable( CreditPayment )postSale( Sale )...
«interface»IInventoryAdapter
...
«interface»ICreditAuthorizationService
Adapter
requestApproval(CreditPayment,TerminalID, MerchantID)...
Interaction Diagram for “Adapter”
:Register : SAPAccountingAdapter
postSale( sale )
makePayment
the Adapter adapts to interfaces in other components
SOAP over HTTP
xxx
...
«actor»: SAPSystem
Sometimes we update domain models to reflect our more refined understanding of domain
Sale
dateTime…...
SalesLineItem
quantity
Contains
1..*
1
TaxLineItem
descriptionpercentageamount
Contains
1..*
1
...
...
...
...
The Factory Pattern
ServicesFactory
accountingAdapter : IAccountingAdapterinventoryAdapter : IInventoryAdaptertaxCalculatorAdapter : ITaxCalculatorAdapter
getAccountingAdapter() : IAccountingAdaptergetInventoryAdapter() : IInventoryAdaptergetTaxCalculatorAdapter() : ITaxCalculatorAdapter...
note that the factory methods return objects typed to an interface rather than a class, so that the factory can return any implementation of the interface
if ( taxCalculatorAdapter == null ) { // a reflective or data-driven approach to finding the right class: read it from an // external property
String className = System.getProperty( "taxcalculator.class.name" ); taxCalculatorAdapter = (ITaxCalculatorAdapter) Class.forName( className ).newInstance();
} return taxCalculatorAdapter;
The Factory Pattern
• Advantages:– Separate the responsibility of complex object creation
into cohesive helper objects– Hide potentially complex creation logic– Allow optimizations to enhance performance
• Object caching: Create objects beforehand and keep them in memory for quick use.
• Re-cycling: Pool of connection threads in a web-server.
• ServicesFactory:– Data-driven design: Class loaded dynamically (at run
time) based on system property.– Can create other adapter classes by changing
property value
The Singleton Pattern
• Issues with the factory pattern:– Who creates the factory itself?– How do we get access to the
factory class from everywhere?
Accessing the Singleton
:Register1
:ServicesFactory
aa = getAccountingAdapterinitialize
...
the ‘1’ indicates that visibility to this instance was achieved via the Singleton pattern
public class Register { public void initialize() { ... aa = ServicesFactory.getInstance().getAccountingAdapter();
... }}
The Singleton Pattern: Implementation and Design Issues
• Lazy initialization:public static synchronized ServicesFactory getInstance() { if (instance == null) instance = new ServicesFactory(); return instance;}
• Eager initialization: ???
The Singleton Pattern: Implementation and Design Issues
• Lazy initialization:public static synchronized ServicesFactory getInstance() { if (instance == null) instance = new ServicesFactory(); return instance;}
• Eager initialization:public class ServicesFactory { private static ServicesFactory instance = new ServicesFactory();
public static ServicesFactory getInstance() { return instance; } }
Singleton Issues
• Lazy vs. eager initialization. Which one is better?– Laziness, of course!
• Creation work (possibly holding on to expensive resources) avoided if instance never created
• Why not make all the service methods static methods of the class itself?– Why do we need an instance of the factory
object and then call its instance methods?
Singleton Issues
• Why not make all the service methods static methods of the class itself?– To permit subclassing: Static methods are not
polymorphic, don’t permit overriding.– Object-oriented remote communication
mechanisms (e.g. Java RMI) only work with instance methods
• Static methods are not remote-enabled.
– More flexibility: Maybe we’ll change our minds and won’t want a singleton any more.
Design Patterns in Interaction Diagrams
:Register accountingAdapter: SAPAccountingAdapter
postSale( sale )
makePayment
SOAP over HTTP
xxx
:Register
1:ServicesFactory
accountingAdapter = getAccountingAdapter
:Store
createcreate
: SAPAccountingAdapter
: Paymentcreate(cashTendered)
«actor»: SAPSystem
create
The Strategy Pattern
PercentDiscountPricingStrategy
percentage : float
getTotal( s:Sale ) : Money
AbsoluteDiscountOverThresholdPricingStrategy
discount : Moneythreshold : Money
getTotal( s:Sale ) : Money
«interface»ISalePricingStrategy
getTotal( Sale ) : Money
{ return s.getPreDiscountTotal() * percentage }
???PricingStrategy
...
...
{pdt := s.getPreDiscountTotal() if ( pdt < threshold ) return pdtelse return pdt - discount }
• Issue: Provide more complex pricing logic.– Pricing strategy varying over time– Example: Different kinds of sales.
Interaction diagram for “Strategy”
:PercentDiscountPricingStrategy
s : Sale
st = getSubtotal
t = getTotal
lineItems[ i ] :SalesLineItem
t = getTotal( s )
pdt = getPreDiscountTotal
{ t = pdt * percentage }
note that the Sale s is passed to the Strategy so that it has parameter visibility to it for further collaboration
loop
Context object
Context object has attribute visibility to its strategy
PercentDiscountPricingStrategy
percentage : float
getTotal( Sale ) : Money
AbsoluteDiscountOverThresholdPricingStrategy
discount : Moneythreshold : Money
getTotal( Sale ) : Money
«interface»ISalePricingStrategy
getTotal( Sale ) : Money
Sale
date...
getTotal()...
1
Sale needs attribute visibility to its Strategy
pricingStrategy
getTotal(){...return pricingStrategy.getTotal( this )
}
Factory for Strategy Object
1PricingStrategyFactory
instance : PricingStrategyFactory
getInstance() : PricingStrategyFactory
getSalePricingStrategy() : ISalePricingStrategygetSeniorPricingStrategy() : ISalePricingStrategy...
{ String className = System.getProperty( "salepricingstrategy.class.name" ); strategy = (ISalePricingStrategy) Class.forName( className ).newInstance(); return strategy; }
Factory Creates Strategy Object on Demand
:Sale
1:PricingStrategyFactory
ps =getSalePricingStrategy
:Register
makeNewSalecreate
create(percent) ps : PercentDiscountPricingStrategy
The “Composite” Design Pattern
The “Composite” Design Pattern
The “Composite” Design Pattern
PercentageDiscountPricingStrategy
percentage : float
getTotal( Sale ) : Money
AbsoluteDiscountOverThresholdPricingStrategy
discount : Moneythreshold : Money
getTotal( Sale ) : Money
«interface»ISalePricingStrategy
getTotal( Sale ) : Money
{ return sale.getPreDiscountTotal() * percentage }
CompositePricingStrategy
add( ISalePricingStrategy )getTotal( Sale ) : Money
{lowestTotal = INTEGER.MAXfor each ISalePricingStrategy strat in pricingStrategies { total := strat.getTotal( sale ) lowestTotal = min( total, lowestTotal ) }return lowestTotal }
1..*
CompositeBestForCustomerPricingStrategy
getTotal( Sale ) : Money
CompositeBestForStorePricingStrategy
getTotal( Sale ) : Money
strategies
All composites maintain a list of contained strategies. Therefore, define a common superclass CompositePricingStrategy that defines this list (named strategies).
Sale
date...
getTotal()...
1
pricingStrategy
{...return pricingStrategy.getTotal( this )}
public abstract class CompositePricingStrategy implements ISalePricingStrategy {
protected List strategies = new ArrayList();
public add (ISalePricingStrategy s) { strategies.add(s); }
public abstract Money getTotal( Sale sale );}
public class ComputeBestForCustomerPricingStrategy extends CompositePricingStrategy {
Money lowestTotal = new Money( Integer.MAX_VALUE );
for (Iterator i = strategies.iterator(); i.hasNext(); ) { ISalePricingStrategy strategy = (ISalePricingStrategy) i.next(); Money total = strategy.getTotal( sale ); lowestTotal = total.min( lowestTotal); } return lowestTotal;}
Collaboration with a Composite
:CompositeBestForCustomerPricingStrategy
s : Sale
st = getSubtotal
t = getTotal
lineItems[ i ] :SalesLineItem
t = getTotal( s )
the Sale object treats a Composite Strategy that contains other strategies just like any other ISalePricingStrategy
x = getTotal( s )
strategies[ j ] :: ISalePricingStrategy
UML: ISalePricingStrategy is an interface, not a class; this is the way in UML 2 to indicate an object of an unknown class, but that implements this interface
{ t = min(set of all x) }
loop
loop
Abstract Classes and Methods in the UML
CompositePricingStrategy
add( ISalePricingStrategy )getTotal( Sale ) : Money
CompositeBestForCustomerPricingStrategy
getTotal( Sale ) : Money
CompositeBestForStorePricingStrategy
getTotal( Sale ) : Money
UML notation: An abstract class is shown with an italicized name
abstract methods are also shown with italics
Creating a Composite Strategy
:Sale
1:PricingStrategyFactory
ps =getSale
PricingStrategy
:Register
makeNewSale create
create ps :CompositeBestForCustomerPricingStrategy
create( percent ) s : PercentageDiscountPricingStrategy
add( s )
Creating the Pricing Strategy for a Customer (Part 1)
Creating the Pricing Strategy for a Customer (Part 2)
s :Sale
1:PricingStrategy
Factory
addCustomerPricingStrategy( s )
ps :CompositeBestForCustomerPricingStrategy
create( pct ) s : PercentageDiscountPricingStrategy
add( s )
by Expert
enterCustomerForDiscount( c : Customer )
c = getCustomer
by Factory and High Cohesion
by Expert ps = getPricingStrategy
pct = getCustomer
Percentage( c )
by High Cohesion
by Factory and Composite
Pass Aggregate Object as Parameter
sd Enter Customer For Discount
Creating the Pricing Strategy for a Customer (Part 2)
s :Sale
1:PricingStrategy
Factory
addCustomerPricingStrategy( s )
ps :CompositeBestForCustomerPricingStrategy
create( pct ) s : PercentageDiscountPricingStrategy
add( s )
by Expert
enterCustomerForDiscount( c : Customer )
c = getCustomer
by Factory and High Cohesion
by Expert ps = getPricingStrategy
pct = getCustomer
Percentage( c )
by High Cohesion
by Factory and Composite
Pass Aggregate Object as Parameter
sd Enter Customer For Discount
WHY?
The “Façade” Design Pattern
• Additional requirement in new iteration of POS: Pluggable business rules
• Example– New sale might be paid by gift certificate.
Only one item can be purchased by a gift certificate– If sale paid by gift certificate, invalidate all payments
with the type of “change due back to customer” different from gift certificate.
– Some sales might be charitable donations by the store. Limited to less than $250 each. Manager must be logged in as cashier for this transaction.
• One of the concerns: What happens to enterItem?
enterItem and Low Impact of Change
• Suppose architect wants low impact of change in pluggable business rules.
• Suppose also that the architect is not sure how to implement the pluggable business rules.– Wants to experiment with different ways of implementing them.
• Solution: The “Façade” design pattern– Problem: Need a common, unified interface to a disparate set of
implementations or interfaces– Solution: Define a single point of contact to the subsystem
containing the implementations• This façade object has a unified interface• Internal implementation details hidden
– Example: The use-case controller objects in your project.
The “Façade” Design Pattern
Domain
+ Sale + Register ...
POSRuleEngine
«interface»- IRule
...
- Rule1
...
- Rule2
...
...
package name may be shown in the tab
visibility of the package element (to outside the package) can be shown by preceding the element name with a visibility symbol
+ POSRuleEngineFacade
instance : RuleEngineFacade
getInstance() : RuleEngineFacade
isInvalid( SalesLineItem, Sale )isInvalid( Payment, Sale )...
*
Façade code example
public class Sale {
public void makeLineItem( ProductDescription desc, int quantity) {SalesLineItem sli = new SalesLineItem( desc, quantity);
// call to the Façade. Notice Singleton pattern if (POSRuleEngineFacede.getInstance().isInvalid(sli,this) )
return; lineItems.add(sli); } //..}
Observer/Publish-Subscribe/Delegation Event Model
• Another requirement: GUI window refreshes its display of the sales total when the total changes– Later: GUI updates display when other data
changes as well
• What’s wrong with the following?– When the Sale object changes its total, it
sends a message to a window, asking it to refresh the display?
Publish-Subscribe Pattern
• What’s wrong with the following?– When the Sale object changes its total, it
sends a message to a window, asking it to refresh the display?
• Answer: The Model-View Separation principle discourages such solutions– Model objects (objects in the domain) should
not know of UI objects– If UI changes, model objects should not need
to change
The Problem
Goal: When the total of the sale changes, refresh the display with the new value
Sale
total...
setTotal( newTotal )...
«interface»PropertyListener
onPropertyEvent( source, name, value )
SaleFrame1
onPropertyEvent( source, name, value )
initialize( Sale sale )...
javax.swing.JFrame
...setTitle()setVisible()...
{ if ( name.equals("sale.total") ) saleTextField.setText( value.toString() );}
Sale
addPropertyListener( PropertyListener lis )publishPropertyEvent( name, value )
setTotal( Money newTotal )...
*propertyListeners
{ total = newTotal; publishPropertyEvent( "sale.total", total ); }
{ propertyListeners.add( lis );}
{ for each PropertyListener pl in propertyListeners pl.onPropertyEvent( this, name, value ); }
{ sale.addPropertyListener( this ) ... }
Fig. 26.23
s : Salesf : SaleFrame1
initialize( s : Sale )
addPropertyListener( sf )
propertyListeners : List<PropertyListener>
add( sf )
Fig. 26.24
s :Sale
setTotal( total )
onPropertyEvent( s, "sale.total", total )
publishPropertyEvent( "sale.total", total )
propertylisteners[ i ] : PropertyListener
loop
Fig. 26.25
: SaleFrame1
onPropertyEvent( source, name, value )
saleTextField: JTextField
setText( value.toString() )
Since this is a polymorphic operation implemented by this class, show a new interaction diagram that starts with this polymorphic version
UML notation: Note this little expression within the parameter. This is legal and consise.
Fig. 26.26
«interface»PropertyListener
onPropertyEvent( source, name, value )
SaleFrame1
onPropertyEvent( source, name, value )
initialize( Sale sale )...
javax.swing.JFrame
...setTitle()setVisible()...
Sale
addPropertyListener( PropertyListener lis )publishPropertyEvent( name, value )
setTotal( Money newTotal )...
*propertyListeners
publishes events to observers/listeners/subscribers
registers them when they ask to subscribe
listens for events observes events subscribes to notification of events
Fig. 26.27
«interface»AlarmListener
onAlarmEvent( source, time )
Beeper
onAlarmEvent( source, time )...
{ display notification dialog box }
AlarmClock
addAlarmnListener( AlarmListener lis )publishAlarmEvent( time )
setTime( newTime )...
*alarmListeners
{ time = newTime; if ( time == alarmTime ) publishAlarmEvent( time ); }
{ alarmListeners.add( lis );}
{ for each AlarmListener al in alarmListeners al.onAlarmEvent( this, time ); }
AlarmWindow
onAlarmEvent( source, time )...
javax.swing.JFrame
...setTitle()setVisible()...
ReliabilityWatchDog
onAlarmEvent( source, time )...
{ beep }
{ check that all required processes are executing normally }