Conducting component-level design, and refactoring
◈ 4 cardsThe five-step task set that turns an architecture into buildable components, the four elaborations inside step 3, component-based development, and why refactoring is hard to fund.
Component-level design is elaborative
Nothing new is invented here. Component-level design takes what the requirements model and the architecture already say and elaborates it until there is enough detail to guide coding and testing — it ends at a level of abstraction close to code, which is the honest way to describe the altitude shift from architectural design. The task set for an object-oriented system has five steps.
Step 1 — identify the design classes in the problem domain. Every analysis class and every architectural component is elaborated: all the attributes and operations required to implement it, and the interfaces it exposes.
Step 2 — identify the design classes in the infrastructure domain. These are not in the requirements model and are often missing from the architecture, which is exactly why the step exists: user-interface components, operating-system components, object and data management components.
Step 3 — elaborate every design class you are not acquiring ready-made. This is where the work is, and it has four parts. (a) Specify message details where classes collaborate — a message expands to a guard condition, a sequence expression, a return value, a name and an argument list. (b) Identify the interfaces for each component; a UML interface is a group of externally visible operations with no internal structure, no attributes and no associations, and every operation in one interface should be cohesive. If an interface performs three unrelated subfunctions, the interface design itself needs refactoring — usually by discovering a class you had not named. (c) Elaborate attributes, giving each a data type, an initial value and any property that constrains it; if a complex attribute recurs across several classes, promote it to a class of its own. (d) Describe the processing flow inside each operation, in pseudocode or an activity diagram, and only where the algorithm is genuinely non-obvious — a simple, widely understood operation needs no further design.
Step 4 — describe the persistent data sources and identify the classes that manage them.
Step 5 — develop the behavioural representations for classes whose behaviour is state-dependent, elaborate the deployment diagrams, and then refactor and reconsider. That last clause is part of the task set, not an afterthought.
Specialized and acquired components
Not every component is designed. Component-based software engineering (CBSE) builds systems from a catalogue: you refine the requirements until candidate components can be identified, search the repository, match each candidate's postconditions against a requirement and check its preconditions, and where nothing matches decide whether to modify the requirement or adapt the closest component. It buys reduced lead time, spread development cost, and quality that comes from a component having been exercised in many systems. It carries real risks in exchange: black-box behaviour you cannot predict, missing interoperability standards that force wrapper code, unknown design assumptions that make testing harder, security exposure from untested combinations, and updates that arrive incompatible. The umbrella name for the underlying problem is architectural mismatch — a disagreement between the assumptions a component makes about its environment and the environment it has been put into.
Component refactoring, and why it is hard to fund
Everybody agrees refactoring components to improve quality is good practice; almost nobody finds it easy to convince a manager to spend money fixing components that currently work. Two things are worth knowing for an exam answer. First, breaking large components into smaller ones does not automatically raise cohesion or lower coupling — you can split badly, and splitting badly makes both worse. Second, error-prone components tend to be architecturally connected to one another, so defects propagate along those connections and maintenance cost accumulates in clusters. That gives a practical signal: components repeatedly checked out and modified together are likely to share a design defect, which is evidence a manager can act on where an appeal to elegance is not.
Worked example — elaborating computeLateFee
From the architecture, LendingService owns the fee rule. Step 3 turns that sentence into something buildable.
- Interface.
assessReturnexposes exactly one operation. An earlier draft also had it build the notification message and update the member's standing — three subfunctions, so not cohesive. Splitting outNotifierandMemberStandingfixed the interface and revealed two classes the architecture had not named. - Message detail.
[loan.state = OVERDUE] 2 : amount := computeLateFee(loan, returnedAt, tier)— the guard says when the message may be sent, the sequence number says when it happens relative to the return event, and the return value is what the caller gets. - Attributes.
graceMinutes: integer = 120 {0 to 1440}is the two-hour hold window as a typed, bounded, initialised attribute rather than a literal buried in the code. - Processing flow. The fee algorithm is neither simple nor widely understood — it has a grace window, a daily cap, a member tier multiplier and a public-holiday exception — so it earns an activity diagram.
recordCondition()two classes away does not; it stores a value, and a designer who draws a diagram for it is producing documentation rather than design.