Chapter 26
GoF Design Patterns
The Adapter Design Pattern
Adapters use interfaces and
polymorphism to add a level of
indirection to varying APIs in other
components.
«interface»
ITaxCalculatorAdapter
getTaxes( Sale ) : List of TaxLineItems
TaxMasterAdapter
GoodAsGoldTaxPro
Adapter
getTaxes( Sale ) : List of TaxLineItems
getTaxes( Sale ) : List of TaxLineItems
«interface»
IAccountingAdapter
postReceivable( CreditPayment )
postSale( Sale )
...
«interface»
ICreditAuthorizationService
Adapter
requestApproval(CreditPayment,TerminalID, MerchantID)
...
«interface»
IInventoryAdapter
SAPAccountingAdapter
postReceivable( CreditPayment )
postSale( Sale )
...
GreatNorthernAccountingAdapter
postReceivable( CreditPayment )
postSale( Sale )
...
...
Interaction Diagram for “Adapter”
:Register
: SAPAccountingAdapter
makePayment
...
SOAP over
HTTP
postSale( sale )
xxx
«actor»
: SAPSystem
the Adapter adapts to
interfaces in other components
Sometimes we update domain models to reflect our more refined
understanding of domain
...
Sale
dateTime
…
...
...
1
Contains
1
Contains
...
...
1..*
Sales
LineItem
quantity
1..*
Tax
LineItem
description
percentage
amount
The Factory Pattern
ServicesFactory
accountingAdapter : IAccountingAdapter
inventoryAdapter : IInventoryAdapter
taxCalculatorAdapter : 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
getAccountingAdapter() : IAccountingAdapter
getInventoryAdapter() : IInventoryAdapter
getTaxCalculatorAdapter() : ITaxCalculatorAdapter
...
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
indicate that only one instance will be created (a
singleton)
The Singleton Pattern
1
ServicesFactory
UML notation: in a
class box, an
underlined attribute or
method indicates a
static (class level)
member, rather than
an instance member
singleton static
attribute
instance : ServicesFactory
accountingAdapter : IAccountingAdapter
inventoryAdapter : IInventoryAdapter
taxCalculatorAdapter : ITaxCalculatorAdapter
singleton
static
method
getInstance() : ServicesFactory
getAccountingAdapter() : IAccountingAdapter
getInventoryAdapter() : IInventoryAdapter
getTaxCalculatorAdapter() : ITaxCalculatorAdapter
...
// static method
public static synchronized ServicesFactory getInstance()
{
if ( instance == null )
instance = new ServicesFactory()
return instance
}
• Issues with the factory
pattern:
– Who creates the factory
itself?
– How do we get access to
the factory class from
everywhere?
Accessing the Singleton
1
:Register
:ServicesFactory
initialize
aa = getAccountingAdapter
...
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
1
:Store
:ServicesFactory
create
create
:Register
accountingAdapter =
getAccountingAdapter
create
: SAPAccounting
Adapter
accountingAdapter:
SAPAccountingAdapter
:Register
makePayment
create(cashTendered)
: Payment
SOAP over
HTTP
postSale( sale )
xxx
«actor»
: SAPSystem
The Strategy Pattern
«interface»
ISalePricingStrategy
getTotal( Sale ) : Money
PercentDiscount
PricingStrategy
AbsoluteDiscount
OverThreshold
PricingStrategy
percentage : float
getTotal( s:Sale ) :
Money
???
PricingStrategy
...
discount : Money
threshold : Money
...
getTotal( s:Sale ) :
Money
{
return s.getPreDiscountTotal() * percentage
}
{
pdt := s.getPreDiscountTotal()
if ( pdt < threshold )
return pdt
else
return pdt - discount
}
• Issue: Provide more complex pricing logic.
– Pricing strategy varying over time
– Example: Different kinds of sales.
Interaction diagram for “Strategy”
s : Sale
lineItems[ i ] :
SalesLineItem
:PercentDiscount
PricingStrategy
t = getTotal
loop
{ t = pdt * percentage }
st = getSubtotal
t = getTotal( s )
pdt = getPreDiscountTotal
Context
object
note that the Sale s is
passed to the Strategy so
that it has parameter
visibility to it for further
collaboration
Context object has attribute visibility to its strategy
Sale needs attribute
visibility to its Strategy
Sale
date
...
getTotal()
...
getTotal()
{
...
return pricingStrategy.getTotal( this )
}
1
pricingStrategy
PercentDiscount
PricingStrategy
«interface»
ISalePricingStrategy
getTotal( Sale ) : Money
AbsoluteDiscount
OverThreshold
PricingStrategy
percentage : float
getTotal( Sale ) : Money
discount : Money
threshold : Money
getTotal( Sale ) : Money
Factory for Strategy Object
1
PricingStrategyFactory
instance : PricingStrategyFactory
getInstance() : PricingStrategyFactory
getSalePricingStrategy() : ISalePricingStrategy
getSeniorPricingStrategy() : ISalePricingStrategy
...
{
String className = System.getProperty( "salepricingstrategy.class.name" );
strategy = (ISalePricingStrategy) Class.forName( className ).newInstance();
return strategy;
}
Factory Creates Strategy Object on Demand
1
:Register
:PricingStrategyFactory
makeNewSale
create
:Sale
ps =
getSalePricingStrategy
create(percent)
ps : PercentDiscount
PricingStrategy
The “Composite”
Design Pattern
sale.getPreDiscountTotal() *
age
getTotal( Sale ) : Money
CompositeBestForCustomer
PricingStrategy
getTotal( Sale ) : Money
{
lowestTotal = INTEGER.MAX
for each ISalePricingStrategy strat in pricingStrategies
{
total := strat.getTotal( sale )
lowestTotal = min( total, lowestTotal )
}
return lowestTotal
}
Composit
Pricin
getTotal( S
The “Composite”
{
...
Design Pattern
return pricingStrategy.getTotal( this )
All composites maintain a list of
contained strategies. Therefore,
define a common superclass
CompositePricingStrategy that
defines this list (named strategies).
}
Sale
date
...
1
«interface»
ISalePricingStrategy
pricingStrategy getTotal( Sale ) : Money
getTotal()
...
PercentageDiscount
PricingStrategy
AbsoluteDiscount
OverThreshold
PricingStrategy
1..*
strategies
Composite
PricingStrategy
percentage : float
getTotal( Sale ) : Money
discount : Money
threshold : Money
add( ISalePricingStrategy )
getTotal( Sale ) : Money
getTotal( Sale ) : Money
{
return sale.getPreDiscountTotal() *
percentage
}
CompositeBestForCustomer
PricingStrategy
getTotal( Sale ) : Money
{
CompositeBestForStore
PricingStrategy
getTotal( Sale ) : Money
The “Composite”
Design Pattern
{
...
return pricingStrategy.getTotal( this )
}
All composites maintain a list of
contained strategies. Therefore,
define a common superclass
CompositePricingStrategy that
defines this list (named strategies).
Sale
date
...
1
«interface»
ISalePricingStrategy
pricingStrategy getTotal( Sale ) : Money
getTotal()
...
PercentageDiscount
PricingStrategy
AbsoluteDiscount
OverThreshold
PricingStrategy
1..*
strategies
Composite
PricingStrategy
percentage : float
getTotal( Sale ) : Money
discount : Money
threshold : Money
add( ISalePricingStrategy )
getTotal( Sale ) : Money
getTotal( Sale ) : Money
{
return sale.getPreDiscountTotal() *
percentage
}
CompositeBestForCustomer
PricingStrategy
getTotal( Sale ) : Money
{
lowestTotal = INTEGER.MAX
for each ISalePricingStrategy strat in pricingStrategies
{
total := strat.getTotal( sale )
lowestTotal = min( total, lowestTotal )
}
return lowestTotal
}
CompositeBestForStore
PricingStrategy
getTotal( Sale ) : Money
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
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
s : Sale
:CompositeBestForCustomer
PricingStrategy
lineItems[ i ] :
SalesLineItem
strategies[ j ] :
: ISalePricingStrategy
t = getTotal
loop
st = getSubtotal
t = getTotal( s )
{ t = min(set of all x) }
loop
the Sale object treats a Composite Strategy that contains
other strategies just like any other ISalePricingStrategy
x = getTotal( s )
Abstract Classes and Methods in the UML
UML notation: An abstract class is
shown with an italicized name
Composite
PricingStrategy
abstract methods are also shown with
italics
add( ISalePricingStrategy )
getTotal( Sale ) : Money
CompositeBestForCustomer
PricingStrategy
getTotal( Sale ) : Money
CompositeBestForStore
PricingStrategy
getTotal( Sale ) : Money
Creating a Composite Strategy
1
:Register
make
NewSale
:PricingStrategyFactory
create
:Sale
ps =
getSale
PricingStrategy
create
ps :CompositeBestForCustomer
PricingStrategy
create( percent )
add( s )
s : PercentageDiscount
PricingStrategy
Creating the Pricing Strategy for a Customer (Part 1)
by Controller
by Expert and
IDs to Objects
:Register
by Expert
s :Sale
:Store
enterCustomerForDiscount( custID )
c = getCustomer( custID )
enterCustomerForDiscount( c : Customer )
ref
Enter Customer
For Discount
Creating the Pricing Strategy for a Customer (Part 2)
sd Enter Customer For Discount
by Expert
by Factory and
High Cohesion
1
s :Sale
:PricingStrategy
Factory
ps :CompositeBestForCustomer
PricingStrategy
enterCustomer
ForDiscount( c : Customer )
addCustomer
PricingStrategy( s )
c = getCustomer
by Expert
ps = getPricing
Strategy
Pass
Aggregate
Object as
Parameter
by High Cohesion
pct =
getCustomer
Percentage( c )
create( pct )
add( s )
by Factory and Composite
s : PercentageDiscount
PricingStrategy
Creating the Pricing Strategy for a Customer (Part 2)
sd Enter Customer For Discount
by Expert
by Factory and
High Cohesion
1
:PricingStrategy
Factory
s :Sale
ps :CompositeBestForCustomer
PricingStrategy
enterCustomer
ForDiscount( c : Customer )
addCustomer
PricingStrategy( s )
c = getCustomer
by Expert
ps = getPricing
Strategy
WHY?
Pass
Aggregate
Object as
Parameter
by High Cohesion
pct =
getCustomer
Percentage( c )
create( pct )
add( s )
by Factory and Composite
s : PercentageDiscount
PricingStrategy
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
package name may be
shown in the tab
Domain
+ Sale
+ Register
...
visibility of the package element (to
outside the package) can be shown
by preceding the element name with a
visibility symbol
POSRuleEngine
+ POSRuleEngineFacade
*
instance : RuleEngineFacade
«interface»
- IRule
...
getInstance() : RuleEngineFacade
isInvalid( SalesLineItem, Sale )
isInvalid( Payment, Sale )
...
- Rule1
...
- Rule2
...
...
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 )
...
{
{
for each PropertyListener pl in propertyListeners
pl.onPropertyEvent( this, name, value );
propertyListeners.add( lis );
}
}
Sale
addPropertyListener( PropertyListener lis )
publishPropertyEvent( name, value )
setTotal( Money newTotal )
...
{
total = newTotal;
publishPropertyEvent( "sale.total", total );
}
javax.swing.JFrame
...
setTitle()
setVisible()
...
propertyListeners
*
«interface»
PropertyListener
onPropertyEvent( source, name, value )
{
if ( name.equals("sale.total") )
saleTextField.setText( value.toString() );
}
SaleFrame1
onPropertyEvent( source, name, value )
{
initialize( Sale sale )
...
}
sale.addPropertyListener( this )
...
Fig. 26.23
sf : SaleFrame1
s : Sale
propertyListeners :
List<PropertyListener>
initialize( s : Sale )
addPropertyListener( sf )
add( sf )
Fig. 26.24
propertylisteners[ i ] :
PropertyListener
s :Sale
setTotal( total )
publishPropertyEvent
( "sale.total", total )
loop
onPropertyEvent( s, "sale.total", total )
Fig. 26.25
Since this is a polymorphic operation implemented by
this class, show a new interaction diagram that starts
with this polymorphic version
: SaleFrame1
saleTextField
: JTextField
onPropertyEvent( source, name, value )
setText( value.toString() )
UML notation: Note this little expression within the
parameter. This is legal and consise.
Fig. 26.26
Sale
addPropertyListener( PropertyListener lis )
publishPropertyEvent( name, value )
setTotal( Money newTotal )
...
javax.swing.JFrame
propertyListeners
...
setTitle()
setVisible()
...
publishes events to
observers/listeners/
subscribers
registers them when
they ask to subscribe
*
«interface»
PropertyListener
onPropertyEvent( source, name, value )
SaleFrame1
onPropertyEvent( source, name, value )
initialize( Sale sale )
...
listens for events
observes events
subscribes to notification of events
Fig. 26.27
{
for each AlarmListener al in alarmListeners
al.onAlarmEvent( this, time );
{
}
alarmListeners.add( lis );
}
AlarmClock
addAlarmnListener( AlarmListener lis )
publishAlarmEvent( time )
{
setTime( newTime )
...
time = newTime;
if ( time == alarmTime )
publishAlarmEvent( time );
}
javax.swing.JFrame
...
setTitle()
setVisible()
...
alarmListeners
*
«interface»
AlarmListener
onAlarmEvent( source, time )
AlarmWindow
Beeper
ReliabilityWatchDog
onAlarmEvent( source, time )
...
onAlarmEvent( source, time )
...
onAlarmEvent( source, time )
...
{
{
{
display notification dialog
box
}
check that all required processes
are executing normally
beep
}
}
© Copyright 2025 Paperzz