The mechanical translations, and the three that are not mechanical.
π Where this lives: object-relational mapping frameworks exist because one of these translations is genuinely hard. Turning a class into code is trivial; turning an object graph with bidirectional references, inheritance and lazy loading into rows in tables is hard enough to have supported an industry for twenty-five years and produced its own famous complaint β "the Vietnam of computer science". Knowing which mappings are mechanical and which are not tells you where to expect trouble. Search "object relational impedance mismatch".
The mechanical mappings
MOST of the design-to-code translation is direct. Work through
it systematically.
A CLASS β A CLASS DEFINITION
Application β public class Application { β¦ }
One public class per file, named identically.
AN ATTRIBUTE β A FIELD
- status : Status β private Status status;
- appliedOn : LocalDate β private final LocalDate
{immutable} appliedOn;
THE `final` COMES FROM THE `{immutable}` CONSTRAINT. Every
property string in the design has a code consequence, and
skipping them loses design information.
A METHOD SIGNATURE β A METHOD DECLARATION
+ issue(no : LicenceNo, o : OfficerId) : Licence
throws NotIssuableException
β
public Licence issue(LicenceNo no, OfficerId officer)
throws NotIssuableException { β¦ }
VISIBILITY MARKERS β ACCESS MODIFIERS
+ β public - β private
# β protected ~ β package-private (no modifier in
Java)
A DERIVED ATTRIBUTE β A METHOD, NOT A FIELD
/ feeDue : Money β public Money feeDue() {
return district.rateOn(appliedOn)
.forCategory(category);
}
THE `/` IS AN INSTRUCTION NOT TO STORE IT. Storing it
reintroduces the drift defect the design was avoiding. If the
computation is expensive, cache it β but that is a separate,
deliberate decision with invalidation to think about.
AN ENUMERATION β AN ENUM
Β«enumerationΒ» Status β public enum Status {
RECEIVED β¦ RECEIVED, UNDER_REVIEW,
APPROVED, REJECTED, ISSUED }
AN Β«interfaceΒ» β AN INTERFACE
Β«interfaceΒ» ApplicationRepository
β public interface ApplicationRepository {
Application find(String no)
throws ApplicationNotFound;
void save(Application app);
}
GENERALISATION β INHERITANCE
Officer βββ CounterOfficer
β public class CounterOfficer extends Officer { β¦ }
An ITALIC class name means `abstract class`.
REALISATION β IMPLEMENTS
PostgresApplicationRepository - -β· ApplicationRepository
β class PostgresApplicationRepository
implements ApplicationRepository { β¦ }
A DEPENDENCY ARROW β A PARAMETER OR LOCAL VARIABLE
It is NOT a field. That is the whole distinction between a
dependency line and a navigable association, from the
visibility topic.
Mapping associations β where multiplicity decides everything
AN ASSOCIATION WITH NAVIGABILITY becomes a FIELD, and the
MULTIPLICITY AT THE FAR END decides the field's type. This is
the most mechanical and most frequently mis-executed mapping.
Application βββΆ 1 District
private final District district; // never null
Application βββΆ 0..1 Payment
private Payment payment; // may be null
β and the design should say what null means. Prefer
Optional<Payment> as a RETURN type; a nullable field is
acceptable internally where the class controls it.
Application ββββΆ 1..* StatusChange {ordered}
private final List<StatusChange> history = new ArrayList<>();
β {ordered} β List, not Set. The constraint chose the
collection type.
Application βββΆ * Endorsement (unordered, unique)
private final Set<Endorsement> endorsements = new HashSet<>();
Register [applicationNo] βββΆ 0..1 Application
private final Map<String, Application> byNumber
= new HashMap<>();
β a QUALIFIED association maps directly to a Map, which is
why the qualifier notation is worth knowing.
THE THREE RULES that keep association mapping honest:
1. MULTIPLICITY 1 MEANS THE FIELD IS NEVER NULL, and the
constructor must enforce it:
public Application(ApplicationNo no, District district) {
this.district = Objects.requireNonNull(district);
}
A `1` multiplicity that permits null in code is a design
assertion the code does not keep.
2. A COLLECTION FIELD IS FINAL AND NEVER EXPOSED:
β public List<StatusChange> getHistory() {
return history; // caller can mutate it
}
β public List<StatusChange> history() {
return List.copyOf(history);
}
ββ public boolean wasEverRejected() { β¦ }
The third is best β expose the question, not the data, per
the visibility topic's collection-leak trap.
3. COMPOSITION (β) MEANS THE WHOLE CREATES AND OWNS THE PART.
In code that means the part is created inside the whole and
never handed in from outside:
β app.addStatusChange(externallyCreatedChange);
β // inside Application
private void recordChange(Status from, Status to,
OfficerId by) {
history.add(new StatusChange(from, to, by,
clock.instant()));
}
The second form makes it impossible to add a history entry
that does not correspond to a real transition β the
invariant is structural rather than hoped for.
A BIDIRECTIONAL ASSOCIATION, if the design genuinely needs one,
requires a single owning side that maintains both:
// in Application, the owner
void attach(Payment p) {
this.payment = p;
p.setApplication(this); // package-private
}
And `setApplication` must not be public, or the two sides can
be made inconsistent from outside. THIS IS WHY THE VISIBILITY
TOPIC ADVISED AGAINST BIDIRECTIONAL ASSOCIATIONS: they cost a
hand-maintained invariant.
The three hard mappings
Three translations are NOT mechanical, and knowing which they
are is the point of this topic.
1. OBJECT TO RELATIONAL β the impedance mismatch.
THE MISMATCHES, each with a real consequence:
IDENTITY objects have identity by reference; rows have
identity by primary key. Two objects loaded
separately from the same row may be distinct
objects representing one entity β hence the
identity-map pattern.
INHERITANCE tables have no inheritance. Three standard
strategies, each with a real cost:
SINGLE TABLE per hierarchy β simple, fast,
but nullable columns for every subclass's
fields
JOINED tables β normalised, but a join per
level on every read
TABLE PER CLASS β no joins, but queries
across the hierarchy need a union
ASSOCIATIONS a bidirectional object reference is one
foreign key, so the object model has two
things to keep consistent where the database
has one
GRANULARITY a value object like Money is two columns, not
a table β so the mapping is not
one-class-one-table
NAVIGATION objects navigate by following references,
which in a database means a query per
reference β the N+1 SELECT PROBLEM:
for (Application a : findAll()) // 1
a.getDistrict().getName(); // + N
100 applications become 101 queries. At 2 ms
each that is 202 ms instead of the ~4 ms a
single join would take β a 50Γ difference
from a mapping detail, and the single most
common performance defect in ORM-based
systems.
THE DESIGN RESPONSE: keep persistence behind the repository
interface (the pure fabrication from the GRASP topic), so
that the mismatch is confined to one layer and the domain
never contains a query.
2. INTERACTION DIAGRAM TO METHOD BODY β mechanical in outline,
judgemental in detail. The next topic covers it: the
messages give you the statements, but the loops,
conditionals, local variables and error handling are
inferred from the use case, not read off the diagram.
3. CONCURRENCY β invisible in every design diagram.
No class diagram or sequence diagram shows that two threads
may execute the same method simultaneously. The design's
`version` field records that the simultaneity question was
ASKED; making it work requires code the diagram cannot
express:
Β· which methods must be synchronised or made immutable
Β· where the transaction boundary sits
Β· whether the collection needs to be concurrent
Β· what happens on an optimistic-lock failure β retry, or
report to the user?
THIS IS WHERE UNSPECIFIED DESIGN BECOMES A DEFECT THAT
APPEARS ONLY UNDER LOAD. From the use case elaboration
topic: question (d) produces the field; only implementation
produces the behaviour.
THE PRACTICAL CONCLUSION: expect the structural mapping to be
quick and the persistence, concurrency and error-handling
mappings to take most of the time. A plan that budgets
implementation as "the easy part because the design is done"
has mis-estimated by exactly the size of these three.
The N+1 query problem is the clearest demonstration that a mapping detail can dominate performance. Following an object reference costs a nanosecond; following it after it has been mapped to a database row costs a network round trip β 202 ms instead of 4 ms for a hundred records, from code that looks entirely innocent.
π Go further: the deepest response to the impedance mismatch is to stop treating one class as one table. Aggregate design asks which cluster of objects must be consistent together β an Application with its status history and payment β and makes that cluster the unit of loading, saving and locking, with references between aggregates held as identifiers rather than object pointers. That single rule eliminates most N+1 problems, removes bidirectional cross-aggregate references, and gives you an obvious transaction boundary. It is why "one aggregate per transaction" is a common design rule. Search "aggregate design rules reference by identity".
π‘ Exam angle: be ready to write class definitions from a design class diagram β that is the near-certain question. Know the mechanical mappings: class to class, attribute to field, visibility markers to access modifiers, enumeration to enum, generalisation to extends, realisation to implements, and a derived attribute to a method rather than a field. State the rule that multiplicity at the far end decides the field type β 1 becomes a non-null field, 0..1 a nullable one, * a collection, {ordered} a List, and a qualified association a Map. Explain the object-relational impedance mismatch and the three inheritance mapping strategies, and note that concurrency appears in no diagram.
Syllabus points
Turning design into code
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.
Related topics in Object-Oriented Design Implementation