Memra

Components, and the principles that govern their design

◈ 5 cards

The object-oriented, traditional and process-related views of a component, then the four class-level principles (OCP, LSP, DIP, ISP) and the three packaging principles.

Three views of the same word

A component is a modular building block of software — in UML's phrasing, a modular, deployable and replaceable part of a system that encapsulates its implementation and exposes a set of interfaces. Components live inside the architecture, so they must collaborate with each other and with entities outside the software's boundary. What the word means in practice depends on who is using it, and a definition answer that gives only the first of the three views is incomplete.

  • The object-oriented view. A component is a set of collaborating classes, each fully elaborated with every attribute and operation needed to implement it, plus the interfaces that let those classes collaborate with other design classes. You get there by elaborating the analysis classes (problem domain) and the infrastructure classes (support services) that the requirements model already named.
  • The traditional view. A component is a functional element — a module — comprising processing logic, the internal data structures that logic needs, and an interface through which it is invoked and passed data. A traditional component plays one of three roles: a control component that coordinates the invocation of others, a problem domain component that implements a function the customer asked for, or an infrastructure component supporting the processing.
  • The process-related view. A component is something you acquire rather than write: an item drawn from a library of proven components or design patterns, complete with a described interface, stated functions and known collaborations, and integrated into the architecture.

The four class-level design principles

The motivation behind all four is the same — make designs amenable to change and stop a change propagating side effects.

Open-Closed (OCP). A component should be open for extension but closed for modification. Specify it so that it can be extended within the functional domain it addresses without editing its internals; the mechanism is an abstraction placed between the component and the thing that will vary.

Liskov Substitution (LSP). Subclasses must be substitutable for their base classes. A component that uses a base class must keep working when handed a derived one, which means every derived class honours the implied contract: the preconditions the client must satisfy, and the postconditions the client is entitled to rely on.

Dependency Inversion (DIP). Depend on abstractions, not on concretions. A component wired straight to other concrete components, instead of to interfaces they satisfy, is correspondingly harder to extend — and code is the ultimate concretion, so skipping design and writing the call directly is already a DIP violation.

Interface Segregation (ISP). Many client-specific interfaces beat one general-purpose interface. Where several kinds of client use one server class, give each category its own interface exposing only the operations it needs; an operation used by two categories simply appears in both.

The three packaging principles

Components do not float free — they get grouped into packages, and grouping is a design decision too. Reuse/Release Equivalency (REP): the granule of reuse is the granule of release, so anything you offer for reuse must come with version control and support for older versions. Common Closure (CCP): classes that change together belong together, so that a change in one functional area touches one package. Common Reuse (CRP): classes that are not reused together should not be grouped together, because an unrelated change to a package forces every dependent package to upgrade and retest.

Worked example — BorrowBox's locker generations

The locker estate is three hardware generations deep: Gen1MechanicalLatch, Gen2MagneticBolt, and Gen3CameraDoor which photographs the compartment on close. The tempting first design puts an if generation == ... chain inside LockerBank, so adding Gen4 next year means editing LockerBank — an OCP violation, and one that will be repeated forever.

The fix is an abstraction between the varying part and the stable one: a LockerDoor interface with open(), status() and selfTest(). A new generation is a new class implementing that interface; LockerBank is untouched. That also satisfies DIP, because LendingService now depends on LockerDoor and not on Gen2MagneticBolt.

LSP is the one that bites in practice here. Gen3CameraDoor.open() refuses to open when its camera is unavailable — it has strengthened the precondition its base class advertised, so a client written against LockerDoor breaks the moment a Gen3 door is substituted. The fix is not a flag; it is to make the camera check a separate selfTest() concern so that open() keeps the contract everybody else relies on.

ISP shows up on ToolCopy. The librarian's console needs recordCondition() and retire(); the member app must never see either. One general-purpose interface would expose retirement to a phone. Two client-specific interfaces over the same class — a member-facing one and a librarian-facing one — cost nothing and remove the possibility.

ComponentObject-orientedLendingServiceTraditionalcomputeLateFeeProcess-relatedQR decoder libGive all three views: the first alone is a partial answer.
The same word, three meanings, and a definition question expects all three. Under each view sits the BorrowBox thing it best describes: a collaborating class cluster, a single functional module, and a component acquired from a library rather than written.
PrincipleWhat it demandsThe BorrowBox mistake itpreventsOpen-Closedopen for extension, closedfor modificationa generation check insideLockerBank, edited forevery new doorLiskov Substitutiona subclass works whereverits base class doesGen3CameraDoor refusing toopen, breaking clients ofLockerDoorDependency Inversiondepend on abstractions, notconcretionsLendingService namingGen2MagneticBolt directlyInterface Segregationmany client-specificinterfaces beat one generalonethe member app seeingretire() on ToolCopy
All four exist for one reason: make the design absorb change without propagating side effects. Read the third column as the test — if you cannot name the mistake a principle prevents, you have memorised it rather than understood it.
NORMAL ~/memra/learn/comp-410/components-and-component-design-principles utf-8 LF