GoF Design Patterns (Ch. 26)

advertisement
GoF Design Patterns
(Ch. 26)
GoF Design Patterns
•
•
•
•
•
•
•
Adapter
Factory
Singleton
Strategy
Composite
Façade
Observer (Publish-Subscribe)
Adapter Pattern (26.1)
Problem: How to resolve incompatible interfaces,
or how to provide a stable interface to similar
components with different interfaces.
Solution: Convert the original interface of a
component into another interface, through an
intermediate adapter object.
Note: the Adapter pattern is an application of
Polymorphism
Adapter Pattern (26.1)
Example: POS needs to adapt several kinds
of external third-party services: tax
calculators, credit authorization services,
inventory systems, accounting systems.
Each has a different API which can’t be
changed.
Fig. 26.1
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 )
...
...
Fig. 26.2
:Register
: SAPAccountingAdapter
makePayment
...
SOAP over
HTTP
postSale( sale )
xxx
«actor»
: SAPSystem
the Adapter adapts to
interfaces in other components
Adapter Pattern (26.1)
Note: Adapter pattern follows GRASP
principles: Polymorphism, Protected
Variation, Indirection
See Fig. 26.3 for conceptual connection
among GRASP principles and Adapter
pattern
Fig. 26.3
Low coupling is a way to achieve protection at a
variation point.
Protected Variation
Mechanism
GRASP
Principles
Polymorphism is a way to achieve protection at a
variation point, and a way to achieve low coupling.
An indirection is a way to achieve low coupling.
Low Coupling
Mechanism
High Cohesion
Mechanism
Indirection
Mechanism
Pure
Fabrication
The Adapter design pattern is a kind of Indirection
and a Pure Fabrication, that uses Polymorphism.
Polymorphism
Example
Adapter
GoF Design
Patterns
Factory Pattern (26.4)
Problem: Who should be responsible for
creating objects when there are special
considerations such as complex creation
logic, a desire to separate creation
responsibilities for better cohesion, etc.?
Solution: Create a Pure Fabrication object
called a Factory that handles the creation.
Factory Pattern (26.4)
• Technically not a GoF pattern
• A variation of GoF Abstract Factory
pattern
Fig. 26.5
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;
Factory Pattern (26.4)
Note: In Fig. 26.5 the implementation of
ServicesFactory illustrates data-driven
design – a form of Protected Variation
Factory Pattern (26.4)
Idea: Define an object whose purpose is to
create objects
Benefits:
– Separate the responsibility of complex
creation into cohesive helper objects
– Can provide object caching (e.g. having only
one random number generator)
Singleton Pattern (26.5)
Problem: Exactly one instance of a class is
allowed. Objects need a global and single
point of access.
Solution: Define a static method of the
class that returns the singleton:
getInstance()
Singleton Pattern (26.5)
Consider the factory and how it is accessed – who creates
the factory?
– Only want one instance of the factory
– Methods may need to be called from various places => how to
make single instance of the factory globally visible
Could pass the ServicesFactory instance around as a
parameter whenever visibility is required or initialize all
objects that need it with a permanent reference to it
Singleton – supports global visibility or a single access
point to a single instance
Fig. 26.6
UML notation: this '1' can optionally be used to
indicate that only one instance will be created (a
singleton)
1
ServicesFactory
UML notation: in a
class box, an
underlined attribute or
method indicates a
static (class level)
member, rather than
an instance member
instance : ServicesFactory
singleton static
attribute
accountingAdapter : IAccountingAdapter
inventoryAdapter : IInventoryAdapter
taxCalculatorAdapter : ITaxCalculatorAdapter
getInstance() : ServicesFactory
getAccountingAdapter() : IAccountingAdapter
getInventoryAdapter() : IInventoryAdapter
getTaxCalculatorAdapter() : ITaxCalculatorAdapter
...
// static method
public static synchronized ServicesFactory getInstance()
{
if ( instance == null )
instance = new ServicesFactory()
return instance
}
singleton
static
method
Singleton Pattern (26.5)
Note: concurrency control in
ServicesFactory – making getInstance()
synchronized
Fig. 26.8 shows how Adapter, Factory,
Singleton patterns are used in design
Fig. 26.8
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
Strategy Pattern (26.7)
Problem: How to design for varying but
related algorithms or policies? How to
design for the ability to change these
algorithms or policies?
Solution: Define each algorithm/strategy in
a separate class with a common interface
Strategy Pattern (26.7)
Example: How to provide more complex
pricing logic, e.g. store-wide discount,
senior citizen discount, employee
discount, etc.
A pricing strategy for a sale can vary, how do
we design for these varying pricing
algorithms?
Fig. 26.9
«interface»
ISalePricingStrategy
getTotal( Sale ) : Money
PercentDiscount
PricingStrategy
AbsoluteDiscount
OverThreshold
PricingStrategy
percentage : float
getTotal( s:Sale ) :
Money
...
discount : Money
threshold : Money
getTotal( s:Sale ) :
Money
{
return s.getPreDiscountTotal() * percentage
}
???
PricingStrategy
{
pdt := s.getPreDiscountTotal()
if ( pdt < threshold )
return pdt
else
return pdt - discount
}
...
Strategy Pattern (26.7)
Example: Create multiple SalePricingStrategy classes each
with a polymorphic getTotal() operation
Note: each getTotal() operation takes a Sale object as a
parameter so that the strategy object can find the prediscount price from the Sale
The implementation of each getTotal() will differ
A strategy object is attached to a context object – the object
to which it applies the algorithm, e.g. Sale
Fig. 26.10
s : Sale
lineItems[ i ] :
SalesLineItem
:PercentDiscount
PricingStrategy
t = getTotal
loop
{ t = pdt * percentage }
st = getSubtotal
t = getTotal( s )
pdt = getPreDiscountTotal
note that the Sale s is
passed to the Strategy so
that it has parameter
visibility to it for further
collaboration
Strategy Pattern (26.7)
Example:
– When a getTotal() message is sent to a Sale it
delegates work to its strategy object
– It is common for the context object to pass a
reference to itself to the strategy object so that
the strategy object has parameter visibility to
the context object
Fig. 26.11
Sale needs attribute
visibility to its Strategy
Sale
date
...
getTotal()
...
1
pricingStrategy
getTotal()
{
...
return pricingStrategy.getTotal( this )
percentage : float
}
getTotal( Sale ) : Money
PercentDiscount
PricingStrategy
«interface»
ISalePricingStrategy
getTotal( Sale ) : Money
AbsoluteDiscount
OverThreshold
PricingStrategy
discount : Money
threshold : Money
getTotal( Sale ) : Money
Strategy Pattern (26.7)
Example: Who should create the strategy?
– Apply the Factory pattern: a
PricingStrategyFactory
– The PricingStrategyFactory is a singleton and
accessed via the Singleton pattern
Fig. 26.12
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;
}
Fig. 26.13
1
:Register
:PricingStrategyFactory
makeNewSale
create
:Sale
ps =
getSalePricingStrategy
create(percent)
ps : PercentDiscount
PricingStrategy
Strategy Pattern (26.7)
Note:
– Strategy is based on Polymorphism and
provides Protected Variation with respect to
changing algorithms
– Strategies are often/usually created by a
Factory
Composite Pattern (26.8)
Problem: How to treat a group or
composition structure of objects the same
way (polymorphically) as a non-composite
(atomic) object?
Solution: Define classes for composite and
atomic objects so that they implement the
same interface.
Composite Pattern (26.8)
Design problem: How to handle multiple conflicting
pricing policies?
Example:
– 20% senior discount
– Preferred customer discount of 15% off sales over
$400
– On Monday, there is a $50 off purchases over $500
– Buy one case of Darjeeling tea, get 15% discount off
everything
Composite Pattern (26.8)
Example (cont.): Pricing strategies determined by
– Time period (Monday)
– Customer type (senior)
– Line item product (Darjeeling tea)
In addition to pre-discount price
• May have multiple co-existing strategies
• Customer type and type of product may need to
be known at time strategy is created (i.e. known
by StrategyFactory)
Composite Pattern (26.8)
Example (cont.): Now how do we design so
that the Sale object does not know if it is
dealing with one or many pricing strategies
and also offer a design for the conflict
resolution
Composite pattern!
Composite Pattern (26.8)
Key feature:
– The composite object contains a list of inner
objects and both the composite object and the
inner objects implement the same interface
Fig. 26.14
{
...
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
Composite Pattern (26.8)
The Sale object treats a Composite Strategy that
contains other strategies as any object that
implements ISalePricingStrategy (i.e., calls its
getTotal(s) operation)
See code pp. 455-456
Notation for abstract classes and abstract methods
– see Fig. 26.16
Fig. 26.16
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
Façade Pattern (26.9)
Problem: A common unified interface to a
disparate set of implementations or interfaces –
such as within a subsystem – is required. There
may be undesirable coupling to many things in
the subsystem, or the implementation of the
subsystem may change. What to do?
Solution: Define a single point of contact to the
subsystem – a façade that wraps the subsystem.
This façade object presents a single unified
interface and is responsible for collaborating
with the subsystem components.
Façade Pattern (26.9)
A façade object serves as a “front-end”
object that is the single point of entry for
the services of a subsystem
Façade provides Protected Variation from
changes in the implementation of the
subsystem
Façade Pattern (26.9)
Example: A “rule engine” subsystem – responsible
for evaluating a set of rules against an operation
and indicating if any rules invalidate the
operation
Example of a rule: If the sale is paid by a gift
certificate, invalidate all payment types of
change due back to the customer except for
another gift certificate.
Also known as pluggable business rules
Façade Pattern (26.9)
Example (cont): Define a façade object to
this subsystem: POSRuleEngineFacade
Calls to this façade placed near the start of
methods that are points for pluggable
rules, see code p. 462
The hidden subsystem could contain any
number of rules
Fig. 26.20
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 Pattern (26.9)
Notes:
– Façade is usually accessed via Singleton
– Similar to Adapter but Adapter applies to
varying interfaces not hiding implementation
of subsystem
Observer Pattern (26.10)
Also known as Publish-Subscribe Pattern
Problem: Different kinds of subscriber objects are
interested in state changes or events of a publisher
object and want to react in their own unique way when
the publisher generates an event. The publisher wants
to maintain low coupling to the subscribers.
Solution: Define a subscriber or listener interface.
Subscribers implement the interface. The publisher can
dynamically register subscribers who are interested in an
event and notify them when an event occurs.
Observer Pattern (26.10)
Example: Want a GUI window to refresh
(update) its display of sale total when the
total changes
See Fig. 26.21
Fig. 26.21
Goal: When the total of the sale
changes, refresh the display with
the new value
Sale
total
...
setTotal( newTotal )
...
Observer Pattern (26.10)
Example (cont):
Simple solution – when Sale changes its
total the object sends a message to the
window telling it to refresh its display
Problem – high coupling between domain
objects and UI objects
Observer Pattern (26.10)
Example (cont):
Want to be able to easily replace UI
objects or even add other UI objects that
can be notified of this event
That is, want model-view separation
Model objects shouldn’t know about view
objects => Protected Variations with
respect to a changing user interface
Fig. 26.22
{
{
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 )
...
Observer Pattern (26.10)
Steps (p. 465)
1.
2.
3.
4.
5.
6.
Define an interface PropertyListener with operation
onPropertyEvent
Define window (SaleFrame1) to implement the interface
When SaleFrame1 is initialized pass it the Sale instance from
which it is displaying the total
SaleFrame1 registers to the Sale instance for notification of
“property events” via the addPropertyListener message
Note that the Sale does not know about SaleFrame1 objects; it
only knows about objects that implement the PropertyListener
interface. (This lowers the coupling of the Sale to the window –
the coupling is only to an interface, not to a GUI class.)
The Sale instance is the publisher of “property events”. When
the total changes, it iterates across all subscribing
PropertyListeners, notifying each
Observer Pattern (26.10)
Notes:
– The SaleFrame1 object is the
observer/subscriber/listener
– Sale is the publisher of property events
– Sale adds SaleFrame1 object to its list of
PropertyListener subscribers – Fig. 26.23
Fig. 26.23
sf : SaleFrame1
s : Sale
propertyListeners :
List<PropertyListener>
initialize( s : Sale )
addPropertyListener( sf )
add( sf )
Observer Pattern (26.10)
Notes:
– When the Sale total changes it iterates over
all registered subscribers and sends the
onPropertyEvent message to each – Figs.
26.24, 26.25
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



listens for events
observes events
subscribes to notification of events
onPropertyEvent( source, name, value )
initialize( Sale sale )
...
Who is the observer, listener, subscriber, and publisher?
Observer Pattern (26.10)
Notes:
– Sale is coupled to view object but only loosely since it
only knows subscribers as implementing the
PropertyListener interface
– In Java this was called Delegation Event Model
Observer pattern is basis for GUI widget event
handling in both Java (AWT & Swing) and .NET
Widgets are publishers of events, other objects
can register as listeners
Observer Pattern (26.10)
Example of AlarmClock:
AlarmClock is publisher of alarm events
Different types of objects can register as
listeners and react to same alarm event in
their own ways
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
}
}
Download