Component Design is one of the concepts on my software developer’s reading list. There are a lot of component design principles (heuristics), so to help me keep them straight, I’ve organized them into this (imperfect) mental model for how they relate to each other:
- Graph: Minimize complexity of the static component graph
- Strive for high cohesion within components, and low coupling between components
- Minimize responsibility duplication: DRY/SPOT (Don’t Repeat Yourself/Single Point of Truth)
- Graph Nodes: Minimize complexity within components (nodes)
- Responsibility: Have a single responsibility by separating: concerns, command/query, option/operand, creation/usage, policy/mechanism, domain model/persistence, domain model/distribution, and facets (who, what, when, where, why, how)
- Inputs / Outputs: Generally try to minimize the number of inputs/outputs. Similar concepts include imports/exports, dependencies/clients, fan-in/fan-out, efferent coupling/afferent coupling
- Implementation: Design code to communicate intent (see the Code Design concept on the reading list)
- Graph Arcs: Minimize complexity in dependencies & relationships between components (nodes)
- Interface Size
- Dependency Binding Mechanism
- Dependency Inversion is Good: Statically depend on interfaces, not implementations (Dependency Inversion Principle). Implementations are provided at runtime.
- Dependency Pyramid is Bad: Statically import implementations, so implementations cannot be changed at runtime.
- Relationship to Testability: Dependencies must be provided at runtime before you’ll be able to use Test Doubles (for more details, see the Test-Driven Development notes in the reading list).
If I had to summarize component design into one sentence, I might say something like: Manage complexity by using interfaces (information hiding and abstraction) to decouple the components in your system.