designing use-case realizations with gof design patterns 徐迎晓 xuyingxiao@126.com...

Post on 31-Dec-2015

275 Views

Category:

Documents

7 Downloads

Preview:

Click to see full reader

TRANSCRIPT

DESIGNING USE-CASEREALIZATIONS WITH GoF

DESIGN PATTERNS

徐迎晓xuyingxiao@126.com

复旦大学软件学院

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

Adapter

问题:如何解决接口不兼容问题,几个类似的构件接口不同,如何为其提供稳定的接口 ?

解决:通过中间的适配器对象,将构件原有接口转换成另一个接口

support external third-party services, tax calculators

support external third-party services, credit authorization services,

support external third-party services,

inventory systems,

support external third-party services,

accounting systems,

Adapter pattern 是 GRASP building blocks 的特例It offers Protected Variations from changing external interfaces or third-party packages through the use of an Indirection object that applies interfaces and Polymorphism.

" Analysis" Discoveries During Design: Domain

Modela list of tax line items are associated with a sale, such as state tax, federal tax, and so forth

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

Factory

who creates the adapters?a domain object (such as a Register)?

如何确定创建哪种 adapter? such as TaxMaster-Adapter or GoodAsGoldTaxProAdapter?

another fundamental design principle

Design to maintain a separation of concerns.一般作为 architectural design principleone of the essential principles in software

engineering modularize or separate distinct concerns

into different areas, so that each has a cohesive purpose.

domain layer 软件对象相对强调纯应用逻辑的职责另一组软件对象负责与外部系统的连接

Context I Problem

当有特殊考虑时如何创建对象 ? ( 如创建对象的逻辑很复杂,或想将创建对象的职责分离出来以获得更高的内聚度等 )

Solution

Create a Pure Fabrication object called a Factory that handles the creation.

public class Factory{ public static Sample creator(){ .... if (which==1) return new MySample(); else if (which==2) return new HisSample(); }}

Sample s;s=Factory.creator();s.xxx();

Factory objects have several advantages: the responsibility of complex creation 分离

到高内聚的 helper objects 中将潜在的复杂的创建对象的逻辑隐藏可进而引入增强性能的内存管理策略,如

object caching or recycling.

Also is an example of a partial data-driven design通过改变属性值来选择创建不同的 adapter

对象Protected Variations : Adapter 的实现类在变化

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

Singleton (GoF)

who creates the factory itself, and how is it accessed?整个过程只需要一个 factory 实例代码中各个地方都可能需要调用 factory 中

的方法来创建实例 -- 〉 Thus, there is a visibility problem: how to get visibility to this single ServicesFactory instance?

Con text I Problem

只允许创建一个实例— it is a “singleton.”需要为对象提供全局的、单点访问

Solution

Define a static method of the class that returns the singleton.

lazy initializaition// Singleton.javapublic class Singleton{

private static Singleton _instance = null;private Singleton(){}

public static Singleton getInstance(){

if (null==_instance)_instance = new Singleton();

return _instance;}

}

保存全类唯一的实例

对客户隐藏构造器

客户只能从此处获得实例

eager initializaition

public class Singleton {

private static Singleton _instance = new Singleton();

public static Singleton getInstance() {

return _instance;

}

}

使用

UML 表示

Fig. 26.7

:Register1

:ServicesFactory

aa = getAccountingAdapterinitialize

...

the ‘1’ indicates that visibility to this instance was achieved via the Singleton pattern

Conclusion

综合使用 Adapter, Factory, and Singleton patterns ,针对外部系统 (tax calculators, accounting systems 等 ) 的各种接口变化提供了 PV

Fig. 26.8

:Register accountingAdapter : SAPAccountingAdapter

postSale ( sale )

makePayment

SOAP over HTTP

xxx

:Register

1:ServicesFactory

accountingAdapter = getAccountingAdapter

:Store

createcreate

: SAPAccountingAdapter

: Paymentcreate (cashTendered )

«actor»: SAPSystem

create

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

Strategy (GoF)

如何提供复杂的 pricing logic (圣诞促销、会员价 ,……)

The pricing strategy(a rule, policy, or algorithm) can vary during different period

Context / Problem

对于变化着的、但又相关的算法和策略如何设计 ? 如何设计才能改变这些算法和策略 ?

Solution

将每个 algorithm/policy/strategy 定义在具有共同接口的不同的类中 .

Fig. 26.10

: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

Creating a Strategy with a Factory

Fig. 26.13

:Sale

1:PricingStrategyFactory

ps =getSalePricingStrategy

:Register

makeNewSalecreate

create(percent) ps : PercentDiscountPricingStrategy

public class Sale{ ISalePricingStrategy pricingStrategy ; double predicounttotal;

double getTotal(){...

predicounttotal=… pricingStrategy=PricingStrategyFactory.getSalePricingStrategy();

return pricingStrategy.getTotal( this ) // pricingStrategy got from PricingStrategyFactory

}}

public interface ISalePricingStrategy { public double getTotal(Sale s);}

public class PricingStrategyFactory{ public ISalePricingStrategy getSalePricingStrategy(){

//… by "salepricingstrategy.class.name" return strategy;

}}

public class PercentDiscountPricingStrategy implements ISalePricingStrategy { public double getTotal( s:Sale ){

return s.getPreDiscountTotal() * percentage }

}

Reading and Initializing the Percentage Value初始值从外部数据源如关系数据库中读取,

以便容易修改

Summary

通过 Strategy and Factory patterns 获得针对定价策略变化的 PV.

对象设计中,基于 Polymorphism and interfaces 的策略模式允许可 pluggable algorithms

策略通常通过 Factory 模式创建

Strategy 和 Factory 有一定的类似 , Strategy 相对简单容易理解 , 并且可以在运行时刻自由切换。 Factory 重点是用来创建对象

Define a family of algorithms

Encapsulate each algorithm

Make the algorithms interchangeable

Let the algorithm vary independently from clients that use it

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

Composition(GoF)

对于多个冲突的定价策略如何处理 ?高级会员打 8 折超过 XXX 元打 8.5 折星期一购买超过 XXX 元立即返还 XX 元买 1 盒龙井,则买所有商品都打 8.5 折

影响 Sale 的定价策略有三个要素

1. time period (Monday)

2. customer type (senior)

3. a particular line item product ( 买 1 盒龙井,则买所有商品都打 8.5 折 )

Context / Problem如何将组合结构和原子结构的对象进行同样

的处理Solution

Define classes for composite and atomic objects so that they implement the same interface.

Fig. 26.15

: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

将对象组合成树形结构以表示“部分 -整体”的层次结构。 C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性

Creating Multiple SalePricingStrategies

什么时候创建这些策略 ?Current store-defined discount

added when the sale is created.Customer type discount,

added when the customer type is communicated to the POS.

Product type discountadded when the product is entered to the sale.

:Sale

1:PricingStrategyFactory

ps =getSale

PricingStrategy

:Register

makeNewSale create

create ps :CompositeBestForCustomerPricingStrategy

create( percent ) s : PercentageDiscountPricingStrategy

add( s )

Customer Type DiscountUse Case : Process Sale

5b. Customer says they are eligible for a discount

1. Cashier signals discount request

2. Cashier enters Customer identification

3. System presents discount total

Introducing the new system operation,

enterCustomerForDiscount(custId)

s :Sale:Register

enterCustomerForDiscount( custID )

by Controllerby Expert and IDs to Objects

:Store

c = getCustomer( custID )

enterCustomerForDiscount( c : Customer )

by Expert

ref Enter CustomerFor Discount

s :Sale

<<singleton>>:PricingStrategy

Factory

addCustomerPricingStrategy( s )

ps :CompositeBestForCustomerPricingStrategy

ISalePricingStrategy

create( pct ) s : PercentageDiscountPricingStrategy

ISalePricingStrategy

add( s )

by Expert

enterCustomerForDiscount( c : Customer

)

originates inanother diagram

c :=getCustomer()

by Factory andHigh Cohesion

by Expert ps := getPricingStrategy()

pct :=getCustomer

Percentage( c )

by High Cohesion

by Factory and Composite

PassAggregateObject asParameter

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

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

Facade (GoF)

NextGen POS Requirementspluggable business rules.

用例场景可预测的点上(如 Process Sale 的makeNewSale, enterItem 等),不同顾客的行为略有不同

如 when a new sale is created,礼券payment type of change duescharitable donation rules

Analysis and Design the specific scenario points across all use

cases must be identified (enterItem, chooseCashChange, ...) 本次只考虑 enterltem

low impact on the existing software components

may want to experiment with different solutions

Context/Problem需要为不同的实现或子系统内部的接口提供公共的、统一的接口 .

外界对子系统内部有很多耦合,或子系统的实现可能会改变

Solution为子系统设计单点访问

– a facade object that wraps the subsystem. The facade object 代表单一的、统一的接口

Creating facade object and calling operations in facade objectFacades are often accessed via a

singleton factory. Calls to the facade should be placed near

the start of the methods that have been defined as the points for pluggable rules

public class Sale

{

public void makeLineItem (ProductSpecification spec, int quantity)

{

SalesLineItem sli = new SalesLineItem(spec, quantity);

if (POSRuleEngineFacade.getInstance().isInvalid(sli, this))

return;

.....

}

...

}

Domain

+ Sale + Register ...

POSRuleEngine

<<interface>>- IRule

...

- Rule1

...

- Rule2

...

...

package name may beshown in the tab

visibility of the package element (tooutside the package) can be shownby preceding the element name with avisibility symbol

+ POSRuleEngineFacade

instance : RuleEngineFacade

getInstance() : RuleEngineFacade

isInvalid( SalesLineItem, Sale )isInvalid( Payment, Sale )...

*1

Observer/Publish-Subscribe/Delegation

Event Model

Goal: When the total of the salechanges, refresh the display withthe new value

Sale

total...

setTotal( newTotal )...

Context/Problem不同的 subscriber 对某个 publisher 的状态变化

和事件感兴趣,当 publisher 生成事件时,每个subscriber 用各自独有的方式响应

Publisher 和 subscribers之间希望低耦合Solution

定义一个 subscriber or listener 接口Subscribers 实现这个接口 .Publisher 可动态地将对自己感兴趣的

subscribers 进行登记事件发生时, notify 所有登记的 subscribers

<<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 ) ...}

Subject

Observer

具体的Observer

update( )

update( )

Subscribing Listener Objects

s :Salesf :SaleFrame1

initialize( s : Sale)

addPropertyListener( sf)

PropertyListener

propertylisteners:Object

add( sf)

PropertyListener UML notation: Recall that

there is no such thing as aninstance of an interface.Therefore, we can generalize(or be vague) and simplyindicate the instances as typeObject, which is assumed tobe the superclass of allclasses. This approach can beused even if the language(such as C++) does notliterally define a root Objectsuperclass.

Fig. 26.23

s : Salesf : SaleFrame1

initialize( s : Sale )

addPropertyListener( sf )

propertyListeners : List<PropertyListener>

add( sf )

Publishing an event(Sale total change)

s :Sale

setTotal( total)

* : onPropertyEvent( s, "sale.total", total)

UML notation: Since themembers of this collection aretyped to the interfacePropertyListener, it is not legal toshow an implementationresponse, such as furthermessaging, since the responsecan vary, depending on the classimplementing this interface.

Therefore, "stop" the messagingat this point, and showimplementions on differentdiagrams.

publishPropertyEvent( "sale.total", total )

propertylisteners:Object

PropertyListener

Fig. 26.24

s :Sale

setTotal( total )

onPropertyEvent( s, "sale.total", total )

publishPropertyEvent( "sale.total", total )

propertylisteners[ i ] : PropertyListener

loop

Receiving event notification

:SaleFrame1

onPropertyEvent( source, name, value)

PropertyListener

saleTextField: JTextField

setText( value.toString())

Since this is a polymorphic operationimplemented by this class, show a newinteraction diagram that starts with thispolymorphic version

UML notation: Note this little expression within theparameter. This is legal and consise.

<<interface>>AlarmListener

onAlarmEvent( source, time )

Beeper

onAlarmEvent( source, time )...

{ display notification dialogbox}

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}

补充内容

OUTLINE

Adapter

Factory

Singleton

Strategy

Composition

Facade

Observer

More on Factory and Singleton

xuyingxiao@126.com

Existing class

class Employee{ //... public void youAreFired() { // lots of code }}a lot of code scattered through the system that looks something like this Employee fred = new Employee(); //... fred.youAreFired();

must-change-all-new-invocations problem

1 Create an interface that has the same name as the existing class, like so:interface Employee{ void youAreFired();}2 Rename the existing class, like so:class Peon implements Employee // used to be Employee{ public void youAreFired() { // lots of code }}3. ?

Factory 1 public interface Employee 2 { void youAreFired(); 3 } 4 5 public static class EmployeeFactory 6 { private Factory(){/*empty*/} 7 8 public static Employee create() 9 { return new Peon();10 }11 }

/*package*/ class Peon implements Employee14 { public void youAreFired()15 { // lots of code16 }17 }

restrict access to the concrete class even further public static class EmployeeFactory{ private EmployeeFactory(){/*empty*/} public static Employee create() { return new Peon(); } private static class Peon implements Employee { public void youAreFired() { // lots of code } }}

anonymous-inner-class version of the factory

public static class EmployeeFactory{ private Factory(){/*empty*/} public static Employee create() { return new Employee() { public void youAreFired() { // lots of code } } }}

Runtime runtimeEnvironment = Runtime.getRuntime();

ButtonPeer peer = Toolkit.getDefaultToolkit().createButton(b);

Thread Problem

1 class Singleton 2 { private static Singleton instance = null; 3 public static instance() 4 { if( instance == null ) 5 { instance = new Singleton(); 6 } 7 return instance; 8 } 9 //...10 }

The easy solution is to use a static initializer

class Singleton2

{ private static Singleton instance = new Singleton();

public instance() { return instance; }

//...

}

The only time the static -initializer approach isn't workable

public static void main( String[] args ){ //... Connection.pointAt( new URL(args[i]) ); //...}

Connection c = Connection.instance();

class Connection{ private static URL server; public static pointAt( URL server ){ this.server = server; } private Connection() { //... establishConnectionTo( server ); //... } private static Connection instance; public synchronized static Connection instance() { if( instance == null ) instance = new Connection(); return connection(); }}

Double-Checked Locking 1 class DoubleChecked 2 { private static volatile DoubleChecked instance = null; 3 private static Object lock = new Object(); 4 5 public static DoubleChecked instance() 6 { if( instance == null ) 7 { synchronized( lock ) 8 { if( instance == null ) 9 instance = new DoubleChecked();10 }11 }12 return instance;13 }14 }

Game of Life

Game of Life

对于处在”生态的格 ,若八个邻居中有 2 个或 3 个”生” ,则继续存活 ,否则将因过于孤独或过于拥挤而死亡 .

对于处在”死”态的空格 ,若八个邻格中有 3 个”生” , 则该格转变为”生” ( 代表繁衍过程 ),否则继续空着 .

Observer Pattern

ObserverObserver uses an anonymous inner class as the Concrete Subject/Subscriber

class Client{ volatile boolean menuItemSelected = false;

public Client( JMenu topMenu ) { // Add an item to the topMenu, and arrange to be notified when it // is selected:

JMenuItem myItem = new JMenuItem( "Hello" ); myItem.addActionListener ( new ActionListener() { public void actionPerformed( ActionEvent e ) { menuItemSelected = true; // process selection } } );

topMenu.add( myItem ); }}

Universe——Observerclass Universe{…Clock.instance().addClockListener( new Clock.Listener() { public void tick() { // code to handle a clock tick goes here } });

}

Clock——Timer 13 public class Clock{ 14 private Timer clock = new Timer(); 15 private TimerTask tick = null; 16

21 private Clock() 22 { createMenus(); 23 }

39 public void startTicking( int millisecondsBetweenTicks ) 40 { if(tick != null) 41 { tick.cancel(); 42 tick=null; 43 } 44 45 if( millisecondsBetweenTicks > 0 ) 46 { tick = new TimerTask() 47 { public void run(){ tick(); } 48 }; 49 clock.scheduleAtFixedRate( tick, 0, millisecondsBetweenTicks); 50 } 51 }

97 public void tick(){ …

Implementing Observer: The Publisher Classclass Publisher1

{ ArrayList subscribers = new ArrayList();

public synchronized void subscribe( Runnable subscriber ) { subscribers.add( subscriber ); }

public synchronized void cancelSubscription( Runnable subscriber ) { subscribers.remove( subscriber); }

private synchronized void fireEvent() // notify all subscribers { for( int i = 0; i < subscribers.size(); ++i ) ((Runnable) subscribers.get(i) ).run(); }}

top related