The named, tested answers to "which object should do this?"
π Where this lives: patterns are why an experienced engineer can join an unfamiliar codebase and orient quickly. Seeing a class called `PaymentGatewayAdapter` or `ReportBuilder` tells you its role immediately, because the name carries a known structure with it. That shared vocabulary is the real deliverable of the patterns movement β more valuable than any individual pattern, and the reason "just use an Observer here" is a complete sentence in a design discussion. Search "design patterns shared vocabulary value".
Responsibilities and GRASP
A RESPONSIBILITY is a contract or obligation of a class. Two
kinds:
DOING doing something itself; initiating action in
other objects; controlling and coordinating
activities in other objects
KNOWING knowing about private encapsulated data; knowing
about related objects; knowing things it can
derive or calculate
RESPONSIBILITY-DRIVEN DESIGN (RDD) is the practice of thinking
about design in terms of responsibilities, roles and
collaborations. Assigning responsibilities well is THE central
skill of object design, and GRASP is the set of principles for
doing it.
GRASP β GENERAL RESPONSIBILITY ASSIGNMENT SOFTWARE PATTERNS
(Larman). Nine patterns; the first five are the ones examined
most.
1. INFORMATION EXPERT (or just "Expert")
PROBLEM What is a general principle for assigning
responsibilities to objects?
SOLUTION Assign a responsibility to the class that has the
INFORMATION NEEDED to fulfil it.
EXAMPLE Who computes the fee due? The Application knows its
category and its district, so it does β not a
FeeCalculator service reaching in for the data.
CONSEQUENCE Encapsulation is maintained, coupling is low,
behaviour is distributed across the classes that
have the data β which produces the rich domain
model of the earlier topic.
WHEN NOT TO USE IT: when applying it would put persistence
or presentation logic inside a domain class. An
Application knows its own data, but making it
responsible for SAVING itself would couple the
domain to the database β so that responsibility
goes elsewhere despite Expert suggesting otherwise.
THIS EXCEPTION IS COMMONLY EXAMINED.
2. CREATOR
PROBLEM Who should be responsible for creating a new
instance of some class?
SOLUTION Assign class B the responsibility to create an
instance of class A if one or more of these is true:
Β· B CONTAINS or compositely aggregates A
Β· B RECORDS A
Β· B CLOSELY USES A
Β· B HAS THE INITIALISING DATA that will be passed
to A's constructor
EXAMPLE Application creates StatusChange, because it
composes them and holds the data.
ALTERNATIVE When creation is complex β conditional logic, a
choice of subtype, an object pool β use a FACTORY
instead.
3. LOW COUPLING
PROBLEM How to support low dependency, low change impact
and increased reuse?
SOLUTION Assign responsibilities so that coupling remains
low. Evaluate alternatives on this basis.
NOTE it is an EVALUATIVE pattern β it does not tell you what
to do, it tells you how to choose between options.
4. HIGH COHESION
PROBLEM How to keep objects focused, understandable and
manageable, and as a side effect support low
coupling?
SOLUTION Assign responsibilities so that cohesion remains
high. Also evaluative.
LOW COUPLING AND HIGH COHESION ARE THE TWO EVALUATIVE
PRINCIPLES against which every other assignment is
judged β the same pair as the design concepts topic,
here applied to individual decisions.
5. CONTROLLER
PROBLEM What first object beyond the UI layer receives and
coordinates a system operation?
SOLUTION Assign the responsibility to a class representing
one of:
Β· the overall SYSTEM, a root object, a device, or
a subsystem β a FAΓADE CONTROLLER
Β· a USE CASE SCENARIO within which the system
event occurs β a USE CASE CONTROLLER, named
<UseCaseName>Handler or Session
BLOATED CONTROLLER is the symptom to watch for: one
controller receiving all system events, with no
delegation, and holding system state it should not.
THE CURE: add more controllers (use-case
controllers), and make sure the controller
DELEGATES rather than doing the work.
6. POLYMORPHISM
Assign responsibility for behaviour that varies by type to
the types themselves, using polymorphic operations, rather
than testing the type with conditionals.
7. PURE FABRICATION
Assign a highly cohesive set of responsibilities to an
artificial class that does NOT represent a domain concept,
when Expert would otherwise violate low coupling or high
cohesion. `ApplicationRepository` is a pure fabrication β
invented purely to keep persistence out of the domain.
8. INDIRECTION
Assign the responsibility for mediating between two
components to an intermediate object, so they are not
directly coupled. An adapter between the domain and a
payment gateway.
9. PROTECTED VARIATIONS
Identify points of predicted variation or instability and
assign responsibilities to create a STABLE INTERFACE around
them.
THIS IS THE DEEPEST GRASP PATTERN and it is Parnas's
information hiding restated: wrap what is likely to change.
Most of the GoF patterns are instances of it.
The GoF patterns you must know
The GANG OF FOUR patterns (Gamma, Helm, Johnson, Vlissides,
1994) are 23 patterns in three categories. Each is described by
NAME, PROBLEM, SOLUTION and CONSEQUENCES.
CREATIONAL β how objects are made
SINGLETON exactly one instance, globally accessible.
USE SPARINGLY: it is global state, which
makes testing hard and hides dependencies.
FACTORY METHOD a method that decides which subclass to
instantiate
ABSTRACT a family of related factories
FACTORY
BUILDER construct a complex object step by step,
separating construction from
representation
PROTOTYPE create by cloning an existing instance
STRUCTURAL β how objects are composed
ADAPTER convert one interface into another the
client expects. THE MOST USEFUL PATTERN in
practice β every integration with an
external system deserves one.
FACADE a single simplified interface to a
subsystem
DECORATOR add responsibilities to an object
dynamically by wrapping it
COMPOSITE treat individual objects and compositions
uniformly (a tree)
PROXY a stand-in controlling access to another
object β for laziness, remoteness or
access control
BRIDGE separate an abstraction from its
implementation so both can vary
FLYWEIGHT share fine-grained objects to save memory
BEHAVIOURAL β how objects interact
STRATEGY encapsulate interchangeable algorithms
behind one interface
OBSERVER notify dependents automatically when state
changes β the publish/subscribe of the
control styles topic
COMMAND encapsulate a request as an object, so it
can be queued, logged or undone
TEMPLATE METHOD define a skeleton algorithm, letting
subclasses fill in steps
STATE represent each state as an object, so
behaviour changes with state
ITERATOR traverse a collection without exposing its
structure
CHAIN OF pass a request along a chain until one
RESPONSIBILITY handler deals with it
MEDIATOR centralise complex communication between
objects
MEMENTO capture and restore an object's state
VISITOR add operations to a structure without
changing its classes
INTERPRETER Β· so on
THE THREE MOST USEFUL IN ORDINARY WORK, and why:
ADAPTER every external dependency should be behind one,
so a change in their API touches one class β
this is Protected Variations, applied
STRATEGY every "we might do this differently later" is a
Strategy; it converts a conditional chain into a
set of classes, per the polymorphism argument
OBSERVER every "when X happens, also do Y and Z" β and it
is the mechanism behind the invariant/reaction
split from the control styles topic
Worked: patterns applied to one real problem
THE PROBLEM: the licence system must support several payment
methods (cash, card, mobile wallet, bank transfer), each with a
different provider, and the set will grow. On payment, several
things must happen: a receipt, an audit entry, a dashboard
update, and eventually an SMS.
THE NAIVE DESIGN:
class PaymentHandler {
void pay(Application a, Money amt, String method) {
if (method.equals("CASH")) {
// record directly
} else if (method.equals("CARD")) {
cardGateway.charge(amt); // their API
} else if (method.equals("WALLET")) {
walletApi.initiate(amt); // a different API
} else if (method.equals("BANK")) {
bankFile.append(amt); // a third shape
}
receiptPrinter.print(...);
auditLog.write(...);
dashboard.increment(amt);
}
}
DIAGNOSE IT with the tools of this section:
Β· fan-out 7, so S = 49
Β· a type-testing conditional chain β the thing Polymorphism
exists to remove
Β· adding a payment method edits this method, so it is not
closed to modification
Β· adding a post-payment action also edits this method β two
unrelated reasons to change one method, which is low
cohesion
Β· four external APIs are referenced directly, so a change
by any provider changes this class
Β· to unit test it you need seven doubles
APPLY THE PATTERNS, each for a stated reason:
1. STRATEGY for the payment methods. One interface, one
implementation per method:
Β«interfaceΒ» PaymentMethodHandler
+ charge(amount : Money) : PaymentReference
CashHandler Β· CardHandler Β· WalletHandler Β· BankHandler
β the conditional chain disappears; a new method adds a
class and edits nothing. (GRASP: Polymorphism.)
2. ADAPTER inside each handler, wrapping the provider's API:
CardHandler β CardGatewayAdapter β the vendor SDK
β the vendor's interface is converted to ours in ONE place.
When they change their API next year, one class changes.
(GRASP: Protected Variations, Indirection.)
3. OBSERVER for the post-payment actions:
PaymentRecorded event β ReceiptListener,
AuditListener,
DashboardListener,
SmsListener (added later)
β adding the SMS requirement adds a listener and edits
nothing. (This is the invariant/reaction split.)
4. FACTORY to select the strategy:
PaymentMethodFactory.handlerFor(method) :
PaymentMethodHandler
β the one place that still knows the set of methods, and it
is a lookup rather than business logic. (GRASP: Creator's
alternative for complex creation.)
5. INFORMATION EXPERT for the domain rules:
application.recordPayment(reference, amount) validates
the amount against its own fee and records the payment
β the rule stays inside the object that owns the data.
THE RESULT:
class PaymentHandler {
PaymentHandler(PaymentMethodFactory factory,
EventPublisher events) { β¦ }
void pay(Application a, Money amt, PaymentMethod m) {
var ref = factory.handlerFor(m).charge(amt);
var payment = a.recordPayment(ref, amt);
events.publish(new PaymentRecorded(payment));
}
}
fan-out 7 β 2 (S: 49 β 4)
doubles to unit test 7 β 2
edits to add a
payment method 1 method β 0 (add a class)
edits to add a
notification 1 method β 0 (add a listener)
classes referencing
a vendor API 1 β 4, but each in ONE adapter
NOTE THE LAST ROW HONESTLY: the pattern version has MORE
classes β the naive version had one method and this has
roughly a dozen types. THAT IS THE COST, and it is real. The
trade is more classes for fewer reasons to edit any one of
them, and it only pays when change is actually expected.
APPLYING ALL THIS TO A SYSTEM WITH ONE PAYMENT METHOD THAT
WILL NEVER CHANGE IS OVER-ENGINEERING β the naive version
would be correct there.
THE JUDGEMENT, stated plainly: a pattern is justified by a
PREDICTED VARIATION. Four payment methods that demonstrably
grow justify Strategy. One method that will not change does
not. "We might need it later" is not a prediction; "we have
four already and marketing has asked for a fifth" is.
The honest row in that table is the class count. The pattern-based design has roughly twelve types where the naive one had a single method β and that cost is only worth paying when the variation is predicted, not imagined. Four payment methods with a fifth requested justifies Strategy; one method that will never change does not.
π Go further: the sharpest critique of pattern enthusiasm is that many GoF patterns are workarounds for missing language features. Strategy is a first-class function; Command is a closure; Iterator is a generator; Template Method is a higher-order function; Singleton is a module. In a language with those features, "applying a pattern" often means writing three lines rather than three classes. That is not an argument against the patterns β the problems they name are real and language-independent β but it is a strong argument against reaching for the class-heavy form by reflex. Search "design patterns are missing language features Norvig".
π‘ Exam angle: define responsibility (doing and knowing) and responsibility-driven design. The near-certain question is GRASP: state the problem and solution for Information Expert, Creator, Controller, Low Coupling and High Cohesion, noting that the last two are evaluative and that Expert has an important exception for persistence and presentation. Know Pure Fabrication, Indirection, Polymorphism and Protected Variations too. For GoF, name the three categories with examples of each and be able to explain Adapter, Strategy, Observer, Singleton and Factory with their consequences. Be ready to argue when a pattern is not justified.
Syllabus points
GRASP / design patterns
Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.