Before A can send a message to B, A must be able to see B.
π Where this lives: visibility is the question dependency injection frameworks exist to answer. Every Spring `@Autowired`, every constructor parameter in a service, every module import is a decision about which objects can reach which β and getting it wrong produces either a tangle where everything can reach everything, or a class that must be handed nine collaborators to do anything. The framework automates the plumbing; it does not make the design decision for you. Search "dependency injection constructor injection why".
The four kinds of visibility
VISIBILITY is the ability of one object to SEE or have a
REFERENCE TO another object. For a sender to send a message to a
receiver, the sender must have visibility to the receiver.
THIS IS A DESIGN QUESTION, not an analysis one. Analysis
associations are undirected β "an Application belongs to a
District" says nothing about which object holds a reference.
Design must decide, because an interaction diagram that shows
A sending a message to B is ASSERTING that A can see B, and
that assertion must be made true.
FOUR KINDS OF VISIBILITY (Larman):
1. ATTRIBUTE VISIBILITY
B is an attribute (a field) of A.
Β· RELATIVELY PERMANENT β it persists as long as A and B
exist
Β· the most common form in an object design
Β· appears in a class diagram as an ASSOCIATION with a
navigability arrow from A to B
class RecordPaymentHandler {
private ApplicationRepository repository; // β B
}
2. PARAMETER VISIBILITY
B is passed as a parameter to a method of A.
Β· RELATIVELY TEMPORARY β it exists only for the duration
of the method
Β· very common, and the WEAKEST FORM OF COUPLING that still
permits a message β so PREFER IT when the relationship
need not be permanent
void recordPayment(Application app, Money amount)
Β· a frequent design move: convert parameter visibility to
attribute visibility by storing the parameter β do this
only if the reference is genuinely needed later
3. LOCAL VISIBILITY
B is declared as a local object within a method of A.
Β· TEMPORARY
Β· two ways it arises:
(a) A creates B locally: `var p = new Payment(...)`
(b) A obtains B from a method call:
`var app = repository.find(no)`
Β· note that (b) is how most local visibility actually
appears in practice
4. GLOBAL VISIBILITY
B is globally visible β a singleton, a static field, a
global variable.
Β· RELATIVELY PERMANENT
Β· THE LEAST DESIRABLE FORM, and the one to avoid. Reasons:
Β· it HIDES THE DEPENDENCY β reading a class's
constructor tells you nothing about what it actually
uses, so you cannot see its coupling
Β· it makes TESTING HARD β you cannot substitute a
double for a global without global mutation
Β· it makes CONCURRENCY HARD β global mutable state is
shared state
The preferred alternative is to achieve the same effect
with a SINGLETON accessed through parameter or attribute
visibility β i.e. inject it, rather than reaching for it.
THE PREFERENCE ORDER, from best to worst coupling consequence:
PARAMETER (temporary, explicit, visible in the signature)
LOCAL (temporary, confined to one method)
ATTRIBUTE (permanent, but explicit and injectable)
GLOBAL (permanent, implicit, untestable)
CHOOSE THE WEAKEST FORM THAT WORKS. That is the visibility
equivalent of "low coupling".
Deciding visibility for a collaboration
TAKE the collaboration from the previous topics and make every
message legal.
THE INTERACTION:
:RecordPaymentHandler
1: app := find(no) βββΆ :ApplicationRepository
2: p := recordPayment(...) βββΆ :Application
3: save(app) βββΆ :ApplicationRepository
4: r := create(p) βββΆ :ReceiptFactory
EVERY ARROW IMPLIES A VISIBILITY REQUIREMENT. Work through
them:
MESSAGE 1 and 3 β the handler must see the repository, for the
whole method and across calls.
β ATTRIBUTE visibility. And crucially, supplied by
CONSTRUCTOR INJECTION rather than created inside:
RecordPaymentHandler(ApplicationRepository repo,
ReceiptFactory receipts)
WHY NOT `new PostgresApplicationRepository()` inside the
handler? Because that would make the handler depend on a
concrete infrastructure class, inverting the layering
rule from the analysis-to-design topic, and it would be
impossible to substitute an in-memory double for testing.
THE CONSTRUCTOR PARAMETER IS THE DEPENDENCY, DECLARED.
MESSAGE 2 β the handler must see the Application.
β LOCAL visibility: it was returned by find() in message 1.
The handler does NOT store it as a field, and should not:
each invocation handles a different application, so
storing it would make the handler stateful and
unusable concurrently.
THIS IS A REAL DESIGN RULE: a handler/controller should be
STATELESS with respect to the request. Recall from the
client-server topic that stateless application servers
are what permit horizontal scaling β the same principle,
one level down.
MESSAGE 4 β the handler must see the receipt factory, and the
factory must see the Payment.
β ATTRIBUTE for the factory (injected), PARAMETER for the
Payment. The factory does not store the payment; it uses
it and returns.
INSIDE message 2 β the Application must see the Payment it
creates and then keep it.
β LOCAL at creation, then ATTRIBUTE once assigned:
Payment p = new Payment(amount, method, officer);
this.payment = p; // now attribute visibility
And the multiplicity from the domain model (0..1) becomes
a single field rather than a collection β the model's
multiplicity DECIDES the field's type, which is the
mechanical correspondence the development cycle topic
described.
THE RESULTING CLASS DECLARATIONS β read directly off the
visibility decisions:
class RecordPaymentHandler {
private final ApplicationRepository repository; // attr
private final ReceiptFactory receipts; // attr
RecordPaymentHandler(ApplicationRepository r,
ReceiptFactory f) { β¦ }
Receipt handle(String no, Money amt,
PaymentMethod m, OfficerId o) {
Application app = repository.find(no); // local
Payment p = app.recordPayment(amt, m, o);// local
repository.save(app);
return receipts.create(p); // param
}
}
class Application {
private Payment payment; // attribute, 0..1
Payment recordPayment(Money amt, PaymentMethod m,
OfficerId o) {
assertPayable(amt);
Payment p = new Payment(amt, m, o); // local, then
this.payment = p; // attribute
return p;
}
}
COUNT THE HANDLER'S PERMANENT DEPENDENCIES: two, both declared
in the constructor, both interfaces. To unit test it you supply
two doubles and no infrastructure β which is exactly the
testability figure the collaboration-diagram topic used to
compare designs.
Navigability, and the traps
NAVIGABILITY on a class diagram is the notation for attribute
visibility:
Handler ββββββββββββββΆ Repository
means the Handler has an attribute referring to the
Repository, and NOT the reverse. An association with no
arrowheads is bidirectional (or unspecified); one with arrows
at both ends is explicitly bidirectional.
BIDIRECTIONAL ASSOCIATIONS ARE EXPENSIVE, and choosing one
should be deliberate:
Β· both objects must be kept CONSISTENT β adding a Payment to
an Application must also set the Payment's application
reference, and every path that modifies one must remember
the other
Β· they create a CYCLE, so neither class can be understood,
tested or deployed without the other
Β· they complicate serialisation and persistence (infinite
recursion when naively serialised)
THE RULE: make an association unidirectional unless BOTH
directions are genuinely traversed by the design. "It might
be useful" is not a reason.
WORKED β should Payment know its Application?
Does any interaction send a message from a Payment to its
Application? Walk the collaborations: no. The Application
creates and holds the Payment; nothing asks a Payment which
application it belongs to.
β UNIDIRECTIONAL, Application βββΆ Payment.
If a later requirement needs the reverse ("find the
application for this gateway reference"), that is a QUERY,
and it belongs to the repository β not a field on Payment.
RESISTING THE BACK-REFERENCE is one of the most useful
habits in object design, and databases teach the opposite
habit because a foreign key is naturally bidirectional.
THE TRAPS:
THE SERVICE LOCATOR / GLOBAL REGISTRY
var repo = ServiceLocator.get(ApplicationRepository.class);
This looks like an improvement on `new` because it is
substitutable β but it retains global visibility's real
problem: the dependency is INVISIBLE IN THE SIGNATURE. You
cannot tell what a class needs without reading its whole
body, and you cannot construct one in a test without
populating a global registry. CONSTRUCTOR INJECTION MAKES
DEPENDENCIES COUNTABLE, which is why a constructor with nine
parameters is a useful warning rather than an inconvenience.
THE CONSTRUCTOR WITH NINE PARAMETERS
Not a visibility problem but a DESIGN problem that visibility
made visible. Nine dependencies means the class has too many
responsibilities β the high fan-out smell from the
collaboration topic. The fix is to split the class, NOT to
hide the dependencies behind a locator. HIDING THE SYMPTOM IS
THE WORST AVAILABLE RESPONSE.
STORING WHAT SHOULD BE A PARAMETER
A handler that stores the current Application as a field
becomes stateful, cannot serve two requests at once, and
accumulates subtle bugs when reused. If a value differs per
invocation, IT IS A PARAMETER.
PASSING WHAT SHOULD BE STORED
The mirror error: threading the same repository through five
method calls as a parameter, because nobody wanted to add a
field. If a value is the same for every invocation, IT IS A
DEPENDENCY, and it belongs in the constructor.
EXPOSING AN INTERNAL COLLECTION
List<StatusChange> getHistory() { return this.history; }
The caller can now add to and remove from the Application's
internal history, so the invariant "every status change is
recorded and immutable" is unenforceable. This is a
visibility leak through an accessor.
THE FIX: return an unmodifiable view, or a copy, or better
still expose the QUESTION rather than the data β
`boolean wasEverRejected()` instead of `getHistory()`. That is
"tell, don't ask" applied to collections.
The rule that resolves most visibility arguments in one line: if a value differs per invocation it is a parameter; if it is the same for every invocation it is a constructor dependency. Storing the first makes a class stateful and unusable concurrently; passing the second threads clutter through every signature.
π Go further: the reason a nine-parameter constructor is a warning rather than a nuisance has a name β constructor injection makes coupling visible, and visible coupling gets fixed. Frameworks that inject into private fields by reflection (`@Autowired` on a field, rather than a constructor parameter) restore the service-locator problem: the class compiles and constructs with no dependencies declared, so nothing pushes back as they accumulate. The current recommendation from the Spring team itself is constructor injection, precisely for this reason. Search "constructor injection over field injection Spring".
π‘ Exam angle: define visibility and state why it is a design rather than an analysis concern. Name and explain all four kinds β attribute, parameter, local, global β with an example of each and whether it is permanent or temporary; this enumeration is the core question. State that global visibility is least desirable and why (hidden dependencies, untestable, concurrency). Explain navigability notation and why bidirectional associations are expensive (consistency maintenance, cycles, serialisation). Be ready to determine the visibility required by a given interaction diagram.
Syllabus points
Attribute, parameter, local, global visibility
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.