GRASP - egnomerator/misc GitHub Wiki
Book Source - Title: Applying UML Patterns : An Introduction to Object -Oriented Analysis, Design and Iterative Development Third Edition Edition
Some initial notes:
-
Cherry-picked GRASP Chapters
- Inside Front Cover: contains GRASP summary
- Chapter 17 - GRASP: Designing Objects with Responsibilities
- Chapter 18 - Object Design Examples with GRASP
- Chapter 25 - GRASP: More Objects with Responsibilities
source--book inside front cover
Pattern/Principle | Description |
---|---|
Information Expert | A general principle of object design and responsibility assignment Assign a responsibility to the information expert--the class that has the information necessary to fulfill the responsibility |
Creator | Who creates? (note that Factory is a common alternate solution) Assign class B the responsibility to create an instance of class A if one of these is true: 1. B contains A 2. B aggregates A 3. B has the initializing data for A 4. B records A 5. B closely uses A |
Controller | What first object beyond the UI layer receives and coordinates ("controls") a system operation? Assign the responsibility to an object representing one of these choices: 1. Represents the overall "system," a "root object," a device that the software is running within, or a major subsystem (these are all variations of a facade controller) 2. Represents a use case scenario within which the system operation occurs (a use-case or session controller) |
Low Coupling (evaluative) | How to reduce the impact of change? Assign responsibilities so that (unnecessary) coupling remains low; use this principle to evaluate alternatives |
High Cohesion (evaluative) | How to keep objects focused, understandable, and manageable, and as a side-effect support Low Coupling? Assign responsibilities so that cohesion remains high; use this to evaluate alternatives |
Polymorphism | Who is responsible when behavior varies by type? When related alternatives or behaviors vary by type (class), assign responsibility for the behavior using polymorphic operations to the types for which the behavior varies |
Pure Fabrication | Who is responsible when you are desperate, and do not want to violate high cohesion and low couping? Assign a highly cohesive set of responsibilities to an artificial or convenience "behavior" class that does not represent a problem domain concept; something made up in order to support high cohesion, low coupling, and reuse |
Indirection | How to assign responsibilities to avoid direct coupling? Assign the responsibility to an intermediate object to mediate between other components or services, so that they are not directly coupled |
Protected Variations | How to assign responsibilities to objects, subsystems, and systems so that the variations or instability in these elements do not have an undesirable impact on other elements? Identify points of predicted variation or instability; assign responsibilities to create a stable "interface" around them |
This is not a concept included in GRASP, but it is repeatedly referenced in the book.
This concept is about the disparity between a domain model and the software design that represents it
- when designing software, we want to achieve a LRG between our domain model and our software objects
- i.e. we want our software objects to be inspired by and closely represent the domain model
- this is a goal because it is consistent with one of the main purposes of OOD--by modeling our software design based on mental models of the real world, we can more easily reason about our code
- this is discussed a bit more in a later section in notes below regarding Responsibility-Driven Design (RDD)
- GRASP: Designing Objects with Responsibilities
Understanding responsibilities is key to good object-oriented design.
- Martin Fowler
The critical design tool for software development is a mind well educated in design principles. It is not the UML or any other technology.
- direct from book
- the UP outlines a number of process inputs and artifact inputs
- in the UP, all elements are optional--so selectively use elements that make sense for the given project
- process inputs
- requirements, use cases, programming experiments (e.g. resolve show-stopper tech questions), a timeboxed first iteration consisting of defined agreed upon scenarios to implement
- artifact inputs
- use case text (defining objects required behavior--UP term is use case realization), system sequence diagrams that identify system operation messages (starting points of interactions), operation contracts, supplementary specification, glossary, domain model
- given one or more of the above discussed inputs developers
- start immediately coding (ideally test-first)
- start some object design UML modeling
- start with another modeling technique such as CRC cards
- the point of the modeling is not UML, the point is to gain additional clarity/understanding through visual representations
- in this phase
- we should already be applying OOD principles such as GRASP, GoF design patterns
- we should have a realistic (agile) attitude about the modeling--it's to achieve shared understanding, not to document
- this phase should be timeboxed to a day--don't emphasize modeling over programming, avoid waterfall mentality
basically whatever is created in the above discussed activities such as UML models, SSDs, prototypes, database models
- the point is that we have deliberately emphasized timeboxing (to a day) along with these characteristics:
- high-level use cases, system-level operations, broad strokes designs
the purpose of these efforts is:
- from these timeboxed efforts we have produced non-finalized reference materials that
- represent a broad shared understanding of the system to develop
- and guide product development
In RDD, whether thinking about a single class or a whole system, think in terms of responsibilities, roles, and collaborations.
-
Doing responsibilities include
- doing something itself, such as creating an object or doing a calculation
- initiating action in other objects
- controlling and coordinating activities in other objects
- example "a
Sale
is responsible for creatingSalesLineItems
"
-
Knowing responsibilities include
- knowing about private encapsulated data
- knowing about related objects
- knowing about things it can derive or calculate
- example "a
Sale
is responsible for knowing itsTotal
"
A purpose of starting the design process (discussed above) by outlining a domain model is to help us determine responsibilities for classes
- we want to achieve low representational gap between our domain model and our software classes
- e.g. drawing from a domain model that indicates a Sale has a time attribute, we could create a
Sale
class with a responsibility of knowing its time with aTime
property- in this example we are using the domain model to both
- determine meaningful names
- help determine responsibility
- low representational gap achieved by
- naming a class by the name of the domain model entity
- determining a responsibility for a class based on the corresponding domain entity
- in this example we are using the domain model to both
Zooming back out a bit, again, determining responsibilities is a process for not just a class but for subsystems and whole systems
- a single method in a
Sale
class can be responsible for "creating a sale" - a subsystem, consisting of hundreds of classes and thousands of methods, can be responsible for "providing access to relational databases"
Collaboration
- to carry out a responsibility, an object may collaborate with other objects
- e.g. a
Sale
class may ask each associatedSalesLineItem
for its amount to get a total
RDD is a metaphor for object design--a community of collaborating responsible objects
What is a pattern?
Most simply, a good pattern is a named and well-known problem/solution pair that can be applied in new contexts, with advice on how to apply it in novel situations and discussion of its trade-offs, implementations, variations, and so forth.
an example of a pattern: | |
---|---|
Pattern Name: | Information Expert |
Problem: | What is a basic principle by which to assign responsibilities to objects? |
Solution: | Assign a responsibility to the class that has the information needed to fulfill it |
A pattern is a learning aid
- a "pattern" vs a "principle": don't get hung up on the term, either one works--the point is that it is a learning aid
- from GoF book: "One person's pattern is another person's primitive building block"
Creator (GRASP)
- application
- the creation responsibility
- who creates object
Square
? - does
Dog
? NO! this doesn't fit our mental model of the domain -
Board
should createSquare
, bc in our Domain model, a Monopoly board contains squares
- who creates object
- but rewind a sec--what about BEFORE classes have been defined?
- again a guiding principle: low representational gap (LRG)
- when we are just starting the design process and need to define software classes, we can look to the domain
- our domain model has a Monolopy board and this board contains squares; so we follow LRG and define a software class
Board
andSquare
and define this containing association
- the creation responsibility
- recap
- by LRG, we have
- defined these software classes--
Board
andSquare
- decided that
Board
containsSquare
s
- defined these software classes--
- by Creator, we determined that
- since
Board
containsSquare
sBoard
will be responsible for creatingSquare
s
- since
- by LRG, we have
Information Expert (GRASP)
- who knows about a square object given a key?
- the board aggregates squares
- the board knows about all squares and should therefore know of and be able to get a square given a key
Low Coupling (GRASP)
- why
Board
overDog
?- why does Expert advise us to make board responsilble for knowing of and getting a square given a key?
- because of coupling--a measure of how strongly one element depends on another
- due to coupling, then a change to one affects the other
- if we make
Dog
be responsible for getting a square given a key, then we increase the overall couping in the system- bc now BOTH
Dog
andBoard
depend onSquare
instead of justBoard
; alsoDog
would depend onBoard
for the map of squares thatBoard
maintains
- bc now BOTH
- Expert encourages designing for low coupling
- given different design choice where, all other things being equal, the design choice have varying degrees of coupling, we should prefer the design choice with lowest coupling
-
Low Coupling tends to reduce the time, effort, and defects in modifying software
-
Controller (GRASP)
The Controller pattern answers this simple question: What first object after or beyond the UI layer should receive the message from the UI layer?
- when a user clicks a button that says "Play Game", should the
Board
handle request to playGame? or something else - there should be an object who's responsibility is to handle a UI request
- it should handle the request via delegation and coordination
- it should NOT do a lot of work itself
High Cohesion (GRASP)
- why should we follow Controller's pattern's advice?
- because it leads to high cohesion and low coupling
- high cohesion
- high cohesion is about keeping the purpose focused, understandable, and manageable, and about supporting low coupling
- a controller than delegates and coordinates has a clearly defined responsibility to accept the request from the UI and ensure that it is handled appropriately
- if this object were to try to do all the work itself, it would become less cohesive and more highly coupled
- by attempting to handle all the lower level implementation details, its purpose would be muddled
Creator
- composition
- discussing this pattern naturally leads to discussing composition
- a composite object is a good candidate for creating its parts
- overlap with Expert
- by Creator, we might determine that a class should create another class since it contains the initializing data for that class
- this overlaps with the Information Expert pattern where we are assigning the creation responsibility because a class has the information needed to create
- factories
- it can be appropriate for a creator to delegate the creation to a helper class
- a Concrete Factory or Abstract Factory could be an appropriate helper class dedicated handling creation
- why
- there might be significant complexities involved in the creation process
- e.g. we might need to recycle instances for memory concerns
- e.g. we might need to fulfill complex initialization requirements at creation time
- there might be significant complexities involved in the creation process
- benefits
- this pattern leads to low coupling, and reuse
- related patterns
- low coupling (GRASP), factory, whole-part (aggregate objects)
Information Expert (or Expert)
-
Expert is not meant to be an obscure or fancy idea; it expresses the common "intuition" that objects do things related to the information they have
- for an expert to fulfill its responsibility, it often needs to collaborate with other "partial" experts
- responsibility assignment metaphors
- "Do it myself" strategy (Peter Coad) - Expert often leads to a software object doing the thing that is normally done to the real-world thing that the software object represents
- "animation" principle (Craig Larman) - real-world inanimate objects "come to life" or are "animated" like in a cartoon in OO software land
- misapplications
- what out for times where Expert leads us the wrong way when we fail to consider the whole picture
- example persisting a sale
- when following Expert, we might think it makes sense for a
Sale
object to be responsible for saving itself to a database since it has much of the information that needs to be saved- acting on this logic would lead to problems in cohesion, couping, and duplication
- we need to remember broader system concerns and adhere to basic architectural principles of separating major system concerns
- keep application logic in one place, database logic in another, etc.
- when following Expert, we might think it makes sense for a
- benefits
- this pattern leads to information encapsulation, low coupling, and high cohesion
- related patterns
- low coupling and high cohesion (both GRASP)
Low Coupling
- broadly
- a class (or system, subsystem, etc.) has high (strong) coupling when it relies on many other classes (or systems, subsystems, etc.)
- this can lead to problems:
- forced local changes due to changes in unrelated code
- difficulty understanding this entity in isolation
- difficult to reuse due to so many dependencies
- low coupling is about reducing the impact of change
-
You cannot obtain an absolute measure of when coupling is too high. What is important is that you can gauge the current degree of coupling and assess whether increasing it will lead to problems. In general, classes that are inherently generic in nature and with a high probability for reuse should have especially low coupling.
- on the other hand, keep in mind that coupling in and of itself is not a problem
-
High coupling to stable elements and to pervasive elements is seldom a problem. For example, a J2EE application can safely couple itself to the Java libraries (java.util, and so on), because they are stable and widespread.
-
- "Pick your battles"
- designing for low coupling in everything is not time well spent
- look for points of realistic high instability or high likelihood of change (Protected Variation)
- benefits
- minimized spread of impact due to a change in one place, easier to understand an entity in isolation, high reusability
- related patterns
- Protected Variation (GRASP)
Controller
- when thinking about Controller, we think at the system level
- system events, system operations
- in a layered systems (UI layer, Business Layer, Persistence Layer, etc.)
- the Controller is the first point beyond the UI layer to handle a system event
- e.g. given a UI event triggered by a button click, the event is passed from the UI layer to the application layer
- the controller handles this event passed from the UI layer
- the controller doesn't do much work, it delegates, it coordinates
-
facade controller: controller as representation of a system
- this is a reasonable choice when there are a few system events to handle
-
use case controller: controller as a use case
- this is a reasonable choice when there are many system events to handle which are given to logically separating into use cases
- this is a GRASP Pure Fabrication
- e.g. we might need this when we begin by trying to use a single controller to represent the system, but over time we find the number of system events to support grows to cause the system controller to be unmanageable and there is a way to logically separate and group these events into use cases
- e.g. in supporting a Web UI, we could have a controller for each UI-CRUD use case, such as managing users, managing documents, managing other resources
- (example implementation: ASP.NET MVC web framework)
- benefits
- "pluggable" UIs (not coupled to one UI)
- ability to improve or strengthen the use case
- e.g. a use case that is legally required to observe a specific order of operations--the controller can ensure this high level order is followed -related patterns
- Pure Fabrication (GRASP)
- Facade, Layers
- Command (this was briefly mentioned as used in message-handling systems)
High Cohesion
- how strongly related and focused are the responsibilities of an element--we want them highly related and focused
- book author notes the yin/yang analogy of high cohesion and low coupling
- is low cohesion ever justified?
- an architect might choose to group loosely related operations to enable an expert to work in one place
- e.g. a team with one SQL expert might benefit from there being only one place to maintain database operations
- note on Coarse-Grained Remote Interface
- calls over a network introduce concerns that justify lower cohesion
- e.g. instead of a 3 separate calls to set different kinds of loosely related data, we might use one call to set all the data to reduce network traffic
- an architect might choose to group loosely related operations to enable an expert to work in one place
- benefits
- easier to understand the design, simpler to maintain and enhance, promotes low coupling, promotes reusability
- related patterns
- low coupling (GRASP)
This chapter goes through use cases with thorough explanations on the software design choices using GRASP
- below are some notable moments as i read through this very practical and helpful chapter
A guideline related to StartUp/initialization code:
-
Do the initialization design last
-
When coding, program at least some Start Up initialization first. But during OO design modeling, consider the Start Up initialization design last, after you have discovered what really needs to be created and initialized. Then, design the initialization to support the needs of other use case realizations.
A general note on UI during the use-case realization
- we are focusing on the domain layer during use-case realization
- we want to be sure that UI implications/requirements are known, but that's all
- decisions about the UI can be delayed for now
A general responsibility assignment guideline
Information Expert should be an early pattern considered unless the problem is a controller or creation problem
A responsibility assignment guideline when multiple design choices seem viable:
When there are alternative design choices, take a closer look at the cohesion and coupling implications of the alternatives, and possibly at the future evolution pressures on the alternatives. Choose an alternative with good cohesion, coupling, and stability in the presence of likely future changes.
When choosing which entity would be responsible for logging a completed sale
- the domain model included a "Store" entity and
- the use case had a post condition requiring the completed sale to be associated with a "Store"
- at this point in the design process, another optional entity was considered--a "SalesLedger"
- it was noted that hypothetically at this point in the design process
- it could be determined that a SalesLedger could be more appropriate than a Store, and if this were determined
- it would be appropriate to update the domain model with the SalesLedger entity
- this type of change is to be expected during this design phase
- it could be determined that a SalesLedger could be more appropriate than a Store, and if this were determined
- this was just a noted example of illustrating that timeboxing the initial design phase
- is not only important for making progress (not getting stuck in waterfall mode--attempting to set all details in stone up front) and be agile
- it's important to keep in mind that--again, in keeping agile--details are not set in stone and changes are to be expected
When determining how the UI layer would be connected to the domain layer for the use case of displaying a running total
- there was a consideration to either
- give the UI a reference to the main Register controller with a
getTotal
call - or give the UI a reference to the current
Sale
object and directly get the total from thisSale
object when required
- give the UI a reference to the main Register controller with a
- and during this consideration it was noted that
- the first option
- pro: maintain lower coupling between the UI layer and domain layer (UI would still only know about the Register controller object)
- con: would expand the interface of the register object making it less cohesive
- the second option
- con: increases the coupling of the UI to the domain layer--it would now know about the
Sale
object- BUT: this was noted as not a major problem, as we will consider the
Sale
object to be a stable object that will be an integral part of the design
- BUT: this was noted as not a major problem, as we will consider the
- con: increases the coupling of the UI to the domain layer--it would now know about the
- the first option
- with that second option's con being negated, revisiting its corollary--the 1st option's pro:
- the value of maintaining lower coupling specifically by not letting the UI layer know about the integral
Sale
object is now significantly diminished
- the value of maintaining lower coupling specifically by not letting the UI layer know about the integral
- which begs revisiting the 1st option's con
- the expansion of the interface of the Register object and the resulting diluted cohesion now seems like the strongest motivating factor
- so in the end, the design choice was to give the UI a reference to the integral
Sale
object
When you reach the initialization design decisions
- what should the class of the initial domain object be?
- Guideline:
Choose as an initial domain object a class at or near the root of the containment or aggregation hierarchy of domain objects. This may be a facade controller, such as Register, or some other object considered to contain all or most other objects, such as a Store
- Guideline:
- in the book's case it chose the
Store
object - these use cases did not consider persistence (again, this is use-case realization, and at a later design point, persistence will be considered)
- with all the use cases considered up to this point, enough information was there to determine what had to be initialized during StartUp and who create what and who would be associated with who
"Who Takes a Turn?"
- this was an interesting section
- the answer is immediately obvious, the
Player
should do this - and yet that immediately obvious answer may be do to faulty intuition
- intuitively, our domain model has a Player entity modeling the real-life players of the game
- players make moves in real life, therefore the
Player
object should do this--but this is not necessarily correct- back to the POS system, we would have a Cashier object that does everything by this logic
- so obviously this line of logic is not enough on its own to justify a choice
- so the question becomes more interesting, and 3 guidelines were considered in this order
- When there are multiple partial information experts to choose from, place the responsibility in the dominant information expert, the object with the majority of the information; this tends to best support Low Coupling
- this didn't really narrow it down--there were 3 candidates with each about a third "expertise"
- When there are alternative design choices, consider the coupling and cohesion impact of each
- this ruled one out, but there were still 2 good candidates
- When there is no clear winner from the alternatives from other guidelines, consider probable future evolution of the software objects and the impact in terms of Information Expert, cohesion, and coupling
- by considering the Information Expert pattern in light of the full game rules, we could see that after all
Player
turns out to be the dominant information expert regarding the information needed for all the turn concerns
- by considering the Information Expert pattern in light of the full game rules, we could see that after all
- When there are multiple partial information experts to choose from, place the responsibility in the dominant information expert, the object with the majority of the information; this tends to best support Low Coupling
- (as expected, there is some overlap of the above guidelines with guidelines mentioned during the POS system use cases)
Command-Query Separation Principle
- CQS is a classic OO principle stating that every method should either be:
- a command method that performs an action (updating, coordinating, …), often has side effects such as changing the state of objects, and is void (no return value);
- or a query that returns data to the caller and has no side effectsit should not permanently change the state of any objects
- this principle was discussed in relation to the
Die
object'sRoll
andgetFaceValue
methods- these are split to follow the CQS
- although it's not uncommon to see implementations that combine these into one method, it's considered undesirable to violate CQS
- my own related thoughts
- pure functions are related to this concept
- if you have a function, you're expecting some result to be returned, you aren't expecting side effects--like changes in object state(s)
- it can be risky to have a function that is not pure, as a caller may be unaware that they are causing side effects when they really are just interested in the result that they are asking a function for
- and reading on, the book also mentions the principle of lease surprise and describes a scenario illustrating the danger of an impure function (although it doesn't mention the term pure function)
Corollary: Do not test for the type of an object and use conditional logic to perform varying alternatives based on type.
A guideline to implementing polymorphism:
Unless there is a default behavior in the superclass, declare a polymorphic operation in the superclass to be {abstract}.
A coupling refactoring indicator:
when object A keeps needing the data in object B it implies either
- object A should hold that data, or
- object B should have the responsibility (by Expert) rather than object A
Guideline: When to Design with Interfaces?
Polymorphism implies the presence of abstract superclasses or interfaces in most OO languages. When should you consider using an interface? The general answer is to introduce one when you want to support polymorphism without being committed to a particular class hierarchy. If an abstract superclass AC is used without an interface, any new polymorphic solution must be a subclass of AC, which is very limiting in single-inheritance languages such as Java and C#. As a rule-of-thumb, if there is a class hierarchy with an abstract superclass C1, consider making an interface I1 that corresponds to the public method signatures of C1, and then declare C1 to implement the I1 interface. Then, even if there is no immediate motivation to avoid subclassing under C1 for a new polymorphic solution, there is a flexible evolution point for unknown future cases.
(emphasis in above paragraph is my own)
As usual, ensure that efforts to achieve flexibility are worth while:
Sometimes, developers design systems with interfaces and polymorphism for speculative "future-proofing" against an unknown possible variation. If the variation point is definitely motivated by an immediate or very probable variability, then the effort of adding the flexibility through polymorphism is of course rational. But critical evaluation is required, because it is not uncommon to see unnecessary effort being applied to future-proofing a design with polymorphism at variation points that in fact are improbable and will never actually arise. Be realistic about the true likelihood of variability before investing in increased flexibility.
Benefits of polymorphism:
- easy to add new variations
- add variations without affecting clients
Related Patterns
- Protected Variations (GRASP), numerous GoF patterns
Assign a highly cohesive set of responsibilities to an artificial or convenience class that does not represent a problem domain conceptsomething made up, to support high cohesion, low coupling, and reuse
-
Representational Decomposition: motivated by the goal to achieve LRG
- focus: designing software classes to represent domain model concepts
-
Behavioral Decomposition: motivated by needs/conditions that lead to a Pure Fabrication
- focus: grouping cohesive functionality
Benefits
- high cohesion, increased reusability, keeps passed-over alternative domain objects cohesive
Related Patterns
- Low Couping (GRASP), High Cohesion (GRASP), GoF patterns, virtually all design patterns are pure fabrications
Warning: don't overuse Pure Fabrication
- overuse of Pure Fabrication can lead to too many behavior objects that depend on separate data structure objects
- this is more like a procedural paradigm rather than OO grouping of associated data and behavior
- this can lead to unnecessary higher coupling
What it is:
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
a note:
Just as many existing design patterns are specializations of Pure Fabrication, many are also specializations of Indirection. Adapter, Facade, and Observer are examples.
Benefits
- lower coupling between components
Related Patterns
- Pure Fabrication (GRASP), Protected Variations (GRASP), Low Coupling (GRASP), "Many GoF patterns, such as Adapter, Bridge, Facade, Observer, and Mediator"
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.
Important note:
This is a very important, fundamental principle of software design! Almost every software or architectural design trick ... is a specialization of Protected Variations.
The author also noted that the progression of maturity in a developer can be observed in how well they are able to apply PV--from learning about the core mechanisms of PV, to attempting to use them everywhere, to astutely determining when scenarios call for PV.
External related resource: A Related Article on Protected Variation (also by the author of this book, Craig Larman)