60-322 Object-Oriented Analysis and Design

58
60-322 Object-Oriented Analysis and Design Week 12, 2009

description

60-322 Object-Oriented Analysis and Design. Week 12, 2009. Pure Fabrication. Problem What object should have the responsibility, when you do not want to violate High Cohesion and Low Coupling, or other goals, but solutions offered by Expert (for example) are not appropriate? - PowerPoint PPT Presentation

Transcript of 60-322 Object-Oriented Analysis and Design

60-322 Object-Oriented Analysis and Design

Week 12, 2009

Mar 25, 2009 2

Pure Fabrication

Problem What object should have the responsibility, when

you do not want to violate High Cohesion and Low Coupling, or other goals, but solutions offered by Expert (for example) are not appropriate?

Object-oriented designs are sometimes characterized by implementing as software classes representations of concepts in the real-world problem domain to lower the representational gap; for example a Sale and Customer class.

Mar 25, 2009 3

Pure Fabrication

However, there are many situations in which assigning responsibilities only to domain layer software classes leads to problems in terms of poor cohesion or coupling, or low reuse potential.

Solution Assign a highly cohesive set of responsibilities to

an artificial or convenience class that does not represent a problem domain concept - something made up, to support high cohesion, low coupling, and reuse.

Mar 25, 2009 4

Pure Fabrication

Such a class is a fabrication of the imagination. Ideally, the responsibilities assigned to this fabrication support high cohesion and low coupling, so that the design of the fabrication is very clean, or pure - hence a pure fabrication.

Finally, in English pure fabrication is an idiom that implies making something up, which we do when we're desperate!

Example NextGen Problem: Saving a Sale Object in a Database

Mar 25, 2009 5

Pure Fabrication

For example, suppose that support is needed to save Sale instances in a relational database.

By Information Expert, there is some justification to assign this responsibility to the Sale class itself, because the sale has the data that needs to be saved.

But consider the following implications:

Mar 25, 2009 6

Pure Fabrication The task requires a relatively large number of supporting

database-oriented operations, none related to the concept of saleness, so the Sale class becomes incohesive. (cohesion)

The Sale class has to be coupled to the relational database interface (such as JDBC in Java technologies), so its coupling goes up. And the coupling is not even to another domain object, but to a particular kind of database interface. (coupling)

Saving objects in a relational database is a very general task for which many classes need support. Placing these responsibilities in the Sale class suggests there is going to be poor reuse or lots of duplication in other classes that do the same thing. (reuse)

Mar 25, 2009 7

Pure Fabrication

Even though Sale is a logical candidate by virtue of Information Expert to save itself in a database, it leads to a design with low cohesion, high coupling, and low reuse potential - exactly the kind of desperate situation that calls for making something up.

A reasonable solution is to create a new class that is solely responsible for saving objects in some kind of persistent storage medium, such as a relational database; call it the PersistentStorage. This class is a Pure Fabricationa figment of the imagination.

Mar 25, 2009 8

Pure Fabrication Notice the name: PersistentStorage is not something one

would find in the Domain Model.

And if a designer asked a business-person in a store, "Do you work with persistent storage objects?" they would not understand.

They understand concepts such as "sale" and "payment."

PersistentStorage is not a domain concept, but something made up or fabricated for the convenience of the software developer.

Mar 25, 2009 9

Pure Fabrication This Pure Fabrication solves the following design

problems:– The Sale remains well-designed, with high cohesion and low

coupling.– The PersistentStorage class is itself relatively cohesive, having

the sole purpose of storing or inserting objects in a persistent storage medium.

– The PersistentStorage class is a very generic and reusable object.

Creating a pure fabrication in this example is exactly the situation in which their use is called for - eliminating a bad design based on Expert, with poor cohesion and coupling, with a good design in which there is greater potential for reuse.

Mar 25, 2009 10

Indirection

Problem Where to assign a responsibility, to avoid direct

coupling between two (or more) things? How to de-couple objects so that low coupling is supported and reuse potential remains higher?

Solution– Assign the responsibility to an intermediate object to

mediate between other components or services so that they are not directly coupled.

The intermediary creates an indirection between the other components.

Mar 25, 2009 11

Indirection

Example

TaxCalculatorAdapter

TaxMasterAdapter

getTaxes ( Sale ) : List< TaxLineItems >

GoodAsGoldTaxProAdapter

getTaxes( Sale ) : List< TaxLineItems >

«interface»ITaxCalculatorAdapter

getTaxes( Sale ) : List< TaxLineItems >

By Polymorphism , multiple tax calculator adapters have their own similar , but varying behavior for adapting to different external tax calculators .

<???> Adapter

...

...

Mar 25, 2009 12

Indirection

Example

TaxCalculatorAdapter

These objects act as intermediaries to the external tax calculators. Via polymorphism, they provide a consistent interface to the inner objects and hide the variations in the external APIs.

By adding a level of indirection and adding polymorphism, the adapter objects protect the inner design against variations in the external interfaces.

Mar 25, 2009 13

Indirection

: Sale : TaxMasterAdapter

taxes = getTaxes( s )

t = getTotal

the adapter acts as a level of indirection to external systems

TCP socket communication

xxx...

«actor»: TaxMasterSystem. . .

Mar 25, 2009 14

Indirection

PersistentStorage The Pure Fabrication example of decoupling the

Sale from the relational database services through the introduction of a PersistentStorage class is also an example of assigning responsibilities to support Indirection.

The PersistentStorage acts as a intermediary between the Sale and the database.

Mar 25, 2009 15

Protected Variations (PV)

Problem How to design objects, subsystems, and systems

so that the variations or instability in these elements does not have an undesirable impact on other elements?

Solution Identify points of predicted variation or instability;

assign responsibilities to create a stable interface around them.– Note: The term "interface" is used in the broadest sense

of an access view; it does not literally only mean something like a Java interface.

Mar 25, 2009 16

Protected Variations-Example

The prior external tax calculator problem and its solution with Polymorphism illustrate Protected Variations.

The point of instability or variation is the different interfaces or APIs of external tax calculators.

The POS system needs to be able to integrate with many existing tax calculator systems, and also with future third-party calculators not yet in existence.

By adding a level of indirection, an interface, and using polymorphism with various ITaxCalculatorAdapter implementations, protection within the system from variations in external APIs is achieved.

Internal objects collaborate with a stable interface; the various adapter implementations hide the variations to the external systems.

Mar 25, 2009 17

Protected Variations-ExampleDiscussion This is a very important, fundamental principle of

software design!

Almost every software or architectural design trick in book - data encapsulation, polymorphism, data-driven designs, interfaces, virtual machines, configuration files, operating systems, and much more - is a specialization of Protected Variations. (previously under the name of information hiding)

PV is a root principle motivating most of the mechanisms and patterns in programming and design to provide flexibility and protection from variations - variations in data, behavior, hardware, software components, operating systems, and more.

Mar 25, 2009 18

Protected Variations-Example The maturation of a developer or architect can be

seen in their growing knowledge of ever-wider mechanisms to achieve PV, to pick the appropriate PV battles worth fighting, and their ability to choose a suitable PV solution.

Mechanisms Motivated by Protected Variations Core Protected Variations Mechanisms

– Data encapsulation, interfaces, polymorphism, and indirection are motivated by PV.

Data-Driven Designs–

Mar 25, 2009 19

Protected Variations-Example

Data-Driven Designs– Data-driven designs cover a broad family of

techniques including reading codes, values, class file paths, class names, and so forth, from an external source in order to change the behavior of, or "parameterize" a system in some way at run-time. Other variants include style sheets, metadata for object-

relational mapping, property files, reading in window layouts, and much more.

– The system is protected from the impact of data, metadata, or declarative variations by externalizing the variant, reading it in, and reasoning with it.

Mar 25, 2009 20

Protected Variations-Example The Liskov Substitution Principle (LSP) LSP formalizes the principle of protection against

variations in different implementations of an interface, or subclass extensions of a superclass.

Liskov formulated the principle succinctly as follows:

– Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.

That is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.

Mar 25, 2009 21

Protected Variations-Example Informally, software (methods, classes, …) that

refers to a type T (some interface or abstract superclass) should work properly or as expected with any substituted implementation or subclass of T - call it S.

For example:public void addTaxes( ITaxCalculatorAdapter calculator,

Sale sale )

{ List taxLineItems = calculator.getTaxes( sale );

// ...

}

Mar 25, 2009 22

Protected Variations-Example

For this method addTaxes, no matter what implementation of ITaxCalculatorAdapter is passed in as an actual parameter, the method should continue to work "as expected." LSP is a simple idea, intuitive to most object developers, that formalizes this intuition.

Structure-Hiding Designs In the first edition of this book, an important, classic

object design principle called Don't Talk to Strangers or the Law of Demeter was expressed as one of the nine GRASP patterns.

Mar 25, 2009 23

Protected Variations-Example

Structure-Hiding Designs Briefly, it means to avoid creating designs that traverse long

object structure paths and send messages (or talk) to distant, indirect (stranger) objects.

Such designs are fragile with respect to changes in the object structures - a common point of instability.

In the second edition the more general PV replaced Don't Talk to Strangers, because the latter is a special case of the former.

That is, a mechanism to achieve protection from structure changes is to apply the Don't Talk to Strangers rules.

Mar 25, 2009 24

Protected Variations-Example

Don't Talk to Strangers places constraints on what objects you should send messages to within a method. It states that within a method, messages should only be sent to the following objects:

– The this object (or self).– A parameter of the method.– An attribute of this.– An element of a collection which is an attribute of this.– An object created within the method.

The intent is to avoid coupling a client to knowledge of indirect objects and the object connections between objects.

Mar 25, 2009 25

Protected Variations-Example

Direct objects are a client's "familiars," indirect objects are "strangers." A client should talk to familiars, and avoid talking to strangers.

Here is an example that (mildly) violates Don't Talk to Strangers. The comments explain the violation.

Mar 25, 2009 26

Protected Variations-Example

This code traverses structural connections from a familiar object (the Sale) to a stranger object (the Payment), and then sends it a message.

It is very slightly fragile, as it depends on the fact that Sale objects are connected to Payment objects. Realistically, this is unlikely to be a problem.

But, consider this next fragment, which traverses farther along the structural path:

Mar 25, 2009 27

Protected Variations-Example

Mar 25, 2009 28

Protected Variations-Example

The example is contrived, but you see the pattern: Traversing farther along a path of object connections in order to send a message to a distant, indirect object - talking to a distant stranger.

The design is coupled to a particular structure of how objects are connected. The farther along a path the program traverses, the more fragile it is.

Why? Because the object structure (the connections) may change.

This is especially true in young applications or early iterations.

Mar 25, 2009 29

Protected Variations-Example However, it is not always necessary to protect against this; it

depends on the instability of the object structure.

In standard libraries (such as the Java libraries) the structural connections between classes of objects are relatively stable.

In mature systems, the structure is more stable. In new systems in early iteration, it isn't stable.

In general, the farther along a path one traverses, the more fragile it is, and thus it is more useful to conform to Don't Talk to Strangers

Mar 25, 2009 30

Protected Variations-Example Most design principles and patterns are

mechanisms for protected variation, including polymorphism, interfaces, indirection, data encapsulation, most of the GoF design patterns, and so on.

Mar 25, 2009 31

Ch 26. Applying GOF Design Patterns

Objectives– Introduce and apply some GoF design

patterns.– Show GRASP principles as a

generalization of other design patterns.

Mar 25, 2009 32

Ch 26. Applying GOF Design Patterns

This chapter explores OO design for use-case realizations for the NextGen case study,

– providing support for external third-party services whose interfaces may vary,

– more complex product pricing rules, and – pluggable business rules.

The emphasis is to show how to apply the Gang-of-Four (GoF) and the more basic GRASP patterns.

Mar 25, 2009 33

The Gang-of-Four Design Patterns GoF design patterns, and their seminal influence,

were first described in Design Patterns, a seminal and extremely popular work (book) that presents 23 patterns useful during object design.

Not all of the 23 patterns are widely used; perhaps 15 are common and most useful.

A thorough study of the Design Patterns book is recommended to grow as an object designer.

Although that book assumes the reader is already an OO designer with significant experience - and has a background in C++ and Smalltalk

Mar 25, 2009 34

Adapter (GoF) Name:

Adapter

Problem:

How to resolve incompatible interfaces, or provide a stable interface to similar components with different interfaces?

Solution: (advice)

Convert the original interface of a component into another interface, through an intermediate adapter object.

Mar 25, 2009 35

Polymorphism

TaxMasterAdapter

getTaxes ( Sale ) : List< TaxLineItems >

GoodAsGoldTaxProAdapter

getTaxes( Sale ) : List< TaxLineItems >

«interface»ITaxCalculatorAdapter

getTaxes( Sale ) : List< TaxLineItems >

By Polymorphism , multiple tax calculator adapters have their own similar , but varying behavior for adapting to different external tax calculators .

<???> Adapter

...

...

Mar 25, 2009 36

Adapter (GoF) The NextGen problem explored to motivate the

Polymorphism pattern and its solution is more specifically an example of the GoF Adapter pattern.

The NextGen POS system needs to support several kinds of external third-party services, including tax calculators, credit authorization services, inventory systems, and accounting systems, among others. Each has a different API, which can't be changed.

A solution is to add a level of indirection with objects that adapt the varying external interfaces to a consistent interface used within the application.

Mar 25, 2009 37

Adapter (GoF)

TaxMasterAdapter

getTaxes( Sale ) : List of TaxLineItems

GoodAsGoldTaxPro

Adapter

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)

...

Mar 25, 2009 38

Some GRASP Principles as a Generalization of Other Patterns

The previous use of the Adapter pattern can be viewed as a specialization of some GRASP building blocks:

– Adapter supports Protected Variations with respect to changing external interfaces or third-party packages through the use of an Indirection object that applies interfaces and Polymorphism.

The Pattern Almanac 2000 lists around 700 (or 500?) design patterns. And many hundreds more have been published since then.

It's important for an experienced designer to know in detail and by memory 50+ of the most important design patterns, but few of us can learn or remember 1,000 patterns ……

Mar 25, 2009 39

Some GRASP Principles as a Generalization of Other Patterns

There's good news: – Most design patterns can be seen as specializations

of a few basic GRASP principles.

As you spend some years applying and reflecting on myriad design patterns, you will increasingly come to feel that it's the underlying themes that are important, and the fine details of Adapter or Strategy or whatever will become secondary.

Mar 25, 2009 40

Some GRASP Principles as a Generalization of Other Patterns

Low coupling is a way to achieve protection at a variation point.

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.

The Adapter design pattern is a kind of Indirection and a Pure Fabrication, that uses Polymorphism.

Protected VariationMechanism

Low Coupling Mechanism

IndirectionMechanism

Adapter

Pure Fabrication

PolymorphismExample

GoF Design Patterns

GRASP Principles

High CohesionMechanism

Mar 25, 2009 41

"Analysis" Discoveries During Design: Domain Model The getTaxes operation returns a list of TaxLineItems.

In addition to being a newly created software class in the Design Model, this is a domain concept.

It is normal and common to discover noteworthy domain concepts and refined understanding of the requirements during design or programming - iterative development supports this kind of incremental discovery.

TaxMasterAdapter

getTaxes ( Sale ) : List< TaxLineItems >

GoodAsGoldTaxProAdapter

getTaxes( Sale ) : List< TaxLineItems >

«interface»ITaxCalculatorAdapter

getTaxes( Sale ) : List< TaxLineItems >

By Polymorphism , multiple tax calculator adapters have their own similar , but varying behavior for adapting to different external tax calculators .

<???> Adapter

...

...

Mar 25, 2009 42

"Analysis" Discoveries During Design: Domain Model

Sale

dateTime…...

SalesLineItem

quantity

Contains

1..*

1

TaxLineItem

descriptionpercentageamount

Contains

1..*

1

...

...

...

...

Mar 25, 2009 43

Factory Pattern

This is also called Simple Factory or Concrete Factory.

This pattern is not a GoF design pattern, but extremely widespread.

It is also a simplification of the GoF Abstract Factory pattern , and often described as a variation of Abstract Factory.

Nevertheless, because of its prevalence and association with GoF, it is presented now.

Mar 25, 2009 44

Factory Pattern

The adapter raises a new problem in the design:

In the prior Adapter pattern solution for external services with varying interfaces, who creates the adapters?

And how to determine which class of adapter to create, such as TaxMaster-Adapter or GoodAsGoldTaxProAdapter?

Mar 25, 2009 45

Factory Pattern

If some domain object creates them, the responsibilities of the domain object are going beyond pure application logic (such as sales total calculations) and into other concerns related to connectivity with external software components.

Mar 25, 2009 46

Factory Pattern

This point underscores another fundamental design principle (usually considered an architectural design principle): Design to maintain a separation of concerns. (Fundamentally, it is an application of the GRASP High Cohesion principle.)

That is, modularize or separate distinct concerns into different areas, so that each has a cohesive purpose.

– For example, the domain layer of software objects emphasizes relatively pure application logic responsibilities, whereas a different group of objects is responsible for the concern of connectivity to external systems

Mar 25, 2009 47

Factory Pattern Therefore, choosing a domain object (such as a

Register) to create the adapters does not support the goal of a separation of concerns, and lowers its cohesion.

A common alternative in this case is to apply the Factory pattern, in which a Pure Fabrication "factory" object is defined to create objects.

Factory objects have several advantages:

Mar 25, 2009 48

Factory Pattern– Separate the responsibility of complex creation into cohesive helper

objects.– Hide potentially complex creation logic.– Allow introduction of performance-enhancing memory management

strategies, such as object caching or recycling.

Name: Factory

Problem: Who should be responsible for creating objects when there are special considerations, such as complex creation logic, a desire to separate the creation responsibilities for better cohesion, and so forth?

Solution: (advice) Create a Pure Fabrication object called a Factory that handles the creation.

Mar 25, 2009 49

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;

Mar 25, 2009 50

Factory Pattern

In the ServicesFactory, the logic to decide which class to create is resolved by reading in the class name from an external source (for example, via a system property if Java is used) and then dynamically loading the class.

This is an example of a partial data-driven design.

This design achieves Protected Variations with respect to changes in the implementation class of the adapter.

Without changing the source code in this factory class, we can create instances of new adapter classes by changing the property value and ensuring that the new class is visible in the Java class path for loading.

Mar 25, 2009 51

Singleton (GoF) Pattern

The ServicesFactory raises another new problem in the design: Who creates the factory itself, and how is it accessed?

First, observe that only one instance of the factory is needed within the process.

Second, quick reflection suggests that the methods of this factory may need to be called from various places in the code, as different places need access to the adapters for calling on the external services.

Thus, there is a visibility problem: How to get visibility to this single ServicesFactory instance?

Mar 25, 2009 52

Singleton (GoF) Pattern

One solution is pass the ServicesFactory instance around as a parameter to wherever a visibility need is discovered for it, or to initialize the objects that need visibility to it, with a permanent reference. This is possible but inconvenient.

An alternative is the Singleton pattern.

Occasionally, it is desirable to support global visibility or a single access point to a single instance of a class rather than some other form of visibility. This is true for the ServicesFactory instance.

Mar 25, 2009 53

Singleton (GoF) Pattern

Name:

Singleton

Problem:

Exactly one instance of a class is allowed - it is a "singleton." Objects need a global and single point of access.

Solution: (advice)

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

Mar 25, 2009 54

Singleton (GoF) Pattern

1ServicesFactory

instance : ServicesFactory

accountingAdapter : IAccountingAdapterinventoryAdapter : IInventoryAdaptertaxCalculatorAdapter : ITaxCalculatorAdapter

getInstance() : ServicesFactory

getAccountingAdapter() : IAccountingAdaptergetInventoryAdapter() : IInventoryAdaptergetTaxCalculatorAdapter() : ITaxCalculatorAdapter...

singleton static attribute

singleton static method

// static methodpublic static synchronized ServicesFactory getInstance(){if ( instance == null ) instance = new ServicesFactory()return instance}

UML notation: in a class box, an underlined attribute or method indicates a static (class level) member, rather than an instance member

UML notation: this '1' can optionally be used to indicate that only one instance will be created (a singleton)

Mar 25, 2009 55

Singleton (GoF) Pattern

The key idea is that class X defines a static method getInstance that itself provides a single instance of X.

With this approach, a developer has global visibility to this single instance, via the static getInstance method of the class, as in this example:

Mar 25, 2009 56

Singleton (GoF) Pattern

Mar 25, 2009 57

Singleton (GoF) Pattern

Seems a good idea to utilize the idea of a static method.

Why not make all the service methods static methods of the class itself, instead of using an instance object with instance-side methods?

For example, what if we add a static method called getAccountingAdapter to ServicesFactory.

But, an instance and instance-side methods are usually preferred for these reasons:

Mar 25, 2009 58

Singleton (GoF) Pattern

Instance-side methods permit subclassing and refinement of the singleton class into subclasses; static methods are not polymorphic (virtual) and don't permit overriding in subclasses in most languages (Smalltalk excluded).

Most object-oriented remote communication mechanisms (for example, Java's RMI) only support remote-enabling of instance methods, not static methods. A singleton instance could be remote-enabled, although that is admittedly rarely done.

A class is not always a singleton in all application contexts. In application X, it may be a singleton, but it may be a "multi-ton" in application Y. It is also not uncommon to start off a design thinking the object will be a singleton, and then discovering a need for multiple instances in the same process. Thus, the instance-side solution offers flexibility.