Sunteți pe pagina 1din 28

GRASP Part 2

‘General Responsibility Assignment Software Patterns’


The GRASP Collection
• 9 simple patterns which capture the fundamental
principles of object design:

Information Expert Polymorphism

Creator Pure Fabrication

Low Coupling Indirection

High Cohesion Protected Variations

Controller
Part 1 Part 2
6. Polymorphism
• Problem
• when related alternatives or behaviours vary by
object type, how should we handle that?

• Solution
• use polymorphic operations to assign the
responsibility for the different behaviours to the
types themselves
But what is the actual problem?
• conditional variation is a common aspect of many
programs

• that variation often involves using different object


types, through differently named, but similar, methods:

• example: steering a vehicle


if vehicle is car then car.turnWheel(dir)
else if vehicle is bicycle then bicycle.turnHandle(dir)
else if...
But what is the actual problem?
• we can design for such type variations using a list
of if-then-else statements, but if a new variation
arrives then we have to modify the case logic:

• if we add a new type of vehicle: hovercraft then


we have to add another else-if statement for its
steering function

• on its own, not too bad, but often new variations


require code changes in several places

• using pluggable components can avoid that


Pluggable Components
• in a client-server relationship, a server
component is pluggable if we can replace it with
another without affecting the client

• we can usually do this by using some sort of


adapter interface
Example: external calculators
• Point of Sale system: suppose there are multiple
3rd party external tax calculators which must be
supported:

• each has a different interface

• each has similar but varying behaviour

• we can use a TaxCalculatorInterface to solve this

from Ch 25 Applying UML & Patterns (Larman)


Example: external calculators

the adapters will


connect to the
various external
systems

from Ch 25 Applying UML & Patterns (Larman)


Example: steering
• so, going back to steering a vehicle, we can apply the pattern and create a
‘Steerable’ interface:
public interface Steerable
{

public void steer( double direction);


}

• and then apply the interface so that callers see a common steer() method:

• if the vehicle type classes have a fixed spec or are external:

• create adapter subclasses for each vehicle type (like the tax example)

• otherwise, if the vehicle type classes are your own code:

• make each of them implement the Steerable interface and its steer()
method - which can make an internal call to the class’ own tailored
turning method
Comments
• a widely applicable pattern

• easy to design and implement

• good for keeping code clean

• good for future-proofing

• but can add considerably to the size of your


project

• so don’t over-use
7. Pure Fabrication
• Problem

• what object should have responsibility when:

• you do not want to violate low coupling and high cohesion

• but solutions offered by Information Expert are not appropriate?


• Solution
• make one up!

• an artificial class which does not represent a problem domain


concept
• but does support low cohesion and high coupling
• hence a ‘pure fabrication’
Is that a good idea?
• to capture the problem (and solve it) correctly, shouldn’t we try to
keep the representational gap low?

• use software objects which represent real-world concepts

• and let them take responsibility?

• i.e. representational decomposition

• yes, but sometimes the practical requirements of system design mean


that assigning responsibility to domain layer classes leads to poor
coupling, cohesion and potential for re-use.

• it can help to create objects to handle a common behaviour, or set


of behaviours

• i.e. behavioural decomposition


Example: Saving Sale instances in a database

• by Information Expert Sale should do this

• Sale has the data

• however:

• the task needs a lot of database operations

• none of which relate to the actual concept of ‘Sale’

• Sale would have to be coupled to a database interface

• so coupling would rise and cross levels


• saving to a database is a general task which many classes would
need to support

• this would lead to high duplication and poor re-use of code


Example: Saving Sale instances in a database

• so we create a new class with the responsibility for saving


objects in persistent storage:

from Ch 25 Applying UML & Patterns (Larman)

• Sale retains high cohesion and low coupling ✓

• PersistentStorage is cohesive ✓

• PersistentStorage is generic and reusable ✓


Comments
• another widely applicable pattern

• again, keeps code clean, good for future-proofing

• but, again, it can be over-used

• in the extreme too many classes would just


represent functions

• they’d need to be heavily coupled to domain


layer classes to get the information which they
need to operate
8. Indirection
• Problem

• when assigning responsibility, how do we avoid


direct coupling between two (or more) objects
to keep coupling low and support their
potential re-use?

• Solution

• we assign the responsibility to an intermediate


object, creating an indirection
Example: external calculators

from Ch 25 Applying UML & Patterns (Larman)

• the adapter adds a level of indirection, protecting the inner design


against variations in the external interfaces

• similarly, in the ‘saving a Sale’ example, PersistentStorage adds a


level of indirection, acting as an intermediary between Sale and the
database
Comments
• we can see how patterns complement each other:

• indirection supports lower coupling

• many indirections are achieved through pure fabrication

• again, use appropriately:

• balance

• cost of coding now

• navigability of design

• against

• value to current coding tasks

• value to future changes and maintenance


9. Protected Variations
• Problem
• how do we design objects or systems so that
variation or instability in those elements does not
impact negatively upon other elements?

• Solution
• identify the points of variation or instability then
assign responsibilities to create an ‘interface’ around
them

• ‘interface’ in it’s most general sense


Example again: external calculators

from Ch 25 Applying UML & Patterns (Larman)

• the different APIs of the external calculators are the points of instability

• both now and in the future (more might be created)

• the adapters protect the internal system objects from such variations
• by providing a stable interface object for them to collaborate with
Mechanisms Motivated by PV
• Core Mechanisms:

• data encapsulation

• interfaces

• polymorphism

• indirection

• Some Other Mechanisms:


• data driven designs

• e.g. reading values from an external source to parameterise a system at runtime

• interpreter driven designs


• language interpreters which read and run virtual machines

• structure hiding designs

• which we’ll look at in detail


Structure Hiding Designs
Don’t Talk to Strangers / The Law of Demeter

• within a method, messages should only be sent to the


following objects:

• this (i.e. the object containing the method)

• a parameter of the method

• an attribute of this

• an element or collection which is an attribute of this

• an object created with the method


Why does this matter?
• designs which allow messages to be sent to
distant ‘stranger’ objects are fragile

• if the structure of the objects, or the


connections between them change, then the
code breaks and has to be re-written

• hiding structure therefore reduces coupling and


can save time and effort when coding
Example: Register
public Class Register
{
private Sale sale;
...
public void printInvoice()
{
...
AccountHolder holder=sale.getPayment().getAccount().getAccountHolder();
...
}
} ~ clever, but fragile
Example: Register
• clever, but fragile:

• the code has to traverse a convoluted path to obtain the


account holder information

• we can ‘see’ the whole path

• if any of the called methods, or (worse) object associations


change, then this code must change too

• the fix:

• add a new public operation to Sale which provides the


information but hides how it was obtained:
• AccountHolder holder = sale.getAccountHolderOfPayment()
Comments
• this pattern underpins most of what you already know about good
object-oriented programming

• and leads to flexible, generic, future-proofed design

• but you can spend too much time applying it

• by adding flexibility and generalisation which is not used now

• and likely never will be

• so, yet again, use common sense

• balance the likely cost of having to change simple, brittle code


against the time which would be spent to make it flexible and
generic
To conclude
• between them the patterns in the GRASP collection
provide capture the basic principles of good object-
oriented software design

• many other patterns you’ll encounter are


specialisations of the patterns we’ll see here

• use them, but don’t over-use them

• (or become paralysed trying to use them)


References & Further Reading
• base structure and figures taken from:

• Applying UML & Patterns (Larman), Chapter 25

• we’ll cover more patterns in the course shortly

• Yet, there are many more patterns than we can reasonably


cover in this course; for a good starting place to learn more,
see:

• http://en.wikipedia.org/wiki/
Software_design_pattern#Classification_and_list

S-ar putea să vă placă și