3.1 Software Design — Cohesion, Coupling & Design Principles
What is software design?
Design is the bridge between requirements (what) and implementation (how). It transforms the SRS into:
- Architecture — high-level structure: subsystems, components, communication
- Module design — each component's internal structure, data, interfaces
- Data design — database schema, file formats
- Interface design — APIs, user interface specs
Requirements (SRS) ─► [DESIGN] ─► Code
│
├── Architectural design (HLD)
├── Module / detailed design (LLD)
├── Data design
└── Interface design
The output is a Software Design Document (SDD).
---
Fundamental Design Principles
These principles, distilled from decades of software engineering practice, guide every design decision:
| Principle | Description | Example |
|---|---|---|
| Modularity | Break the system into manageable, well-defined modules | Library = {Member, Book, Loan, Report} modules |
| Abstraction | Hide implementation, expose only essential interface | A "Stack" exposes push/pop; doesn't reveal it's an array |
| Information hiding | Each module hides its internal state from others | Member module owns the member table — no one else touches it |
| Encapsulation | Bundle data and operations together | A Java class encapsulating its fields with getters/setters |
| Functional independence | High cohesion + low coupling | Each module does one thing well |
| Refinement | Top-down decomposition | "Issue Book" → check eligibility → check availability → create loan |
| Separation of concerns | Different aspects in different modules | UI ≠ business logic ≠ data access |
| Anticipation of change | Make likely-to-change parts replaceable | Currency code in config, not hard-coded |
Exam tip: Functional independence is the most important principle and is measured by cohesion (within a module) and coupling (between modules).
---
Cohesion — "togetherness" within a module
Cohesion is a measure of how strongly the elements within a single module belong together. Higher cohesion is better — each module should do one well-defined job.
Stevens, Myers and Constantine (1974) defined 7 levels of cohesion, ranked worst to best:
| Level | Name | Definition | Example |
|---|---|---|---|
| 1 (worst) | Coincidental | Elements are grouped randomly | "Utility" module containing date helpers, network sniffer, log rotator |
| 2 | Logical | Elements perform similar logical tasks | Input handler that reads from keyboard, file, network — based on a flag |
| 3 | Temporal | Elements executed at the same time | "Initialization" module — open log, allocate buffers, set defaults |
| 4 | Procedural | Elements follow a sequence of execution | Read input → process → print output |
| 5 | Communicational | Elements act on the same data | Process all calculations on a customer record |
| 6 | Sequential | Output of one element is input of next | Read raw scan → de-skew → OCR → validate |
| 7 (best) | Functional | All elements contribute to a single, well-defined task | calculateInterest() does nothing but interest |
Memory aid (acronym)
Come Let's Take Physics Course Since Final exams (Coincidental, Logical, Temporal, Procedural, Communicational, Sequential, Functional)
Why high cohesion matters
- A high-cohesion module is easy to name — its name describes one job
- It is easy to test — one job means a focused test suite
- It is easy to reuse — clear contract
- It is easy to maintain — changes are localised
---
Coupling — "dependence between modules"
Coupling is a measure of how strongly modules depend on each other. Lower coupling is better — modules should be able to change independently.
5 levels of coupling, worst to best:
| Level | Name | Definition | Example |
|---|---|---|---|
| 1 (worst) | Content coupling | One module modifies another's internal data or code | Goto into the middle of another function; access private vars |
| 2 | Common coupling | Modules share global data | Multiple modules read/write a global config variable |
| 3 | Control coupling | One module passes a flag that controls another's behaviour | process(flag) where flag changes the algorithm |
| 4 | Stamp coupling | Modules pass entire structures when only parts are needed | updateAge(student) when only age is needed |
| 5 (best) | Data coupling | Modules exchange only needed data items | updateAge(id, age) — minimal interface |
Additional categories (sometimes listed)
| Level | Name | Description |
|---|---|---|
| External coupling | Dependence on external file format or protocol | Hard-coded XML format |
| Message coupling (OO) | Lowest in OO — modules communicate only via method calls | Sending message account.deposit(100) |
Memory aid
Could Cats Chase Some Dogs? (Content, Common, Control, Stamp, Data — worst to best)
Why low coupling matters
- Modules are independently testable
- Modules are independently modifiable
- Modules can be replaced without affecting others
- Bugs are localised
---
Cohesion vs Coupling — quick comparison
| Aspect | Cohesion | Coupling |
|---|---|---|
| Refers to | Within a module | Between modules |
| Direction | Should be high | Should be low |
| Measures | How focused a module is | How dependent modules are |
| Goal of good design | Maximise | Minimise |
Golden rule: High cohesion, low coupling. Every good design optimises this trade-off.
---
Classification of Design
Architectural styles
| Style | Description | Example |
|---|---|---|
| Layered (n-tier) | Stack of layers, each uses the one below | Presentation → Business → Data |
| Client-Server | Server provides services, clients consume | Database server, web server |
| Pipe-and-Filter | Stream-processing components | Unix pipes; ETL pipelines |
| Event-Driven | Components react to events | UI frameworks; pub-sub |
| Microservices | Small autonomous services communicate over network | Netflix, Amazon |
| Repository / Blackboard | Components communicate via shared data store | Compiler symbol table |
| MVC (Model-View-Controller) | Separates data, presentation, control | Most web frameworks |
Design strategies
| Strategy | Process |
|---|---|
| Top-down | Start from system, decompose into sub-problems |
| Bottom-up | Start with utilities, compose into system |
| Hybrid (sandwich) | Middle-out: identify components, then top-down and bottom-up |
| Object-oriented design (OOD) | Identify classes and their interactions |
| Function-oriented design (FOD) | Identify functions and data flows (uses DFD) |
---
Structured Design — Function-Oriented
Originating with Yourdon & Constantine (1979), structured design is the classical function-oriented approach:
1. Draw DFD for the system.
2. Identify transform / transaction centres.
3. Map the DFD into a structure chart (modules + calls).
4. Refine each module independently.
Structure chart is a tree of modules:
Library System
/ | \
Member Book Loan
/ \ / \ / | \
Add Get Add Get Issue Return Renew
Each branch is a module; arrows can indicate data flow ("data couples") and control ("control couples").
---
Key Terms — Lesson 3.1
The vocabulary below covers cohesion, coupling, design principles, architectural styles, and the OO design extensions you need to be fluent in for any Unit-III PYQ.
Software Design — The phase that transforms requirements (SRS) into a blueprint (SDD) for construction — defining the architecture, modules, data structures, interfaces, and algorithms. Design sits between requirements and implementation in the SDLC.
Software Design Document (SDD) — The deliverable of the design phase. Typically split into High-Level Design (HLD) describing major subsystems and their interfaces, and Low-Level Design (LLD) describing internal class/method designs, data structures, and algorithms.
Architectural Design / High-Level Design (HLD) — The system-wide design decisions: choice of architectural style (layered, microservices, MVC, etc.), major subsystems, their responsibilities, and how they communicate. HLD answers "what are the boxes and arrows?"
Detailed Design / Low-Level Design (LLD) — The internal design of each component identified in HLD — classes, methods, data structures, algorithms, error-handling. LLD answers "what is inside each box?"
Modularity — The principle of decomposing the system into manageable, self-contained modules with well-defined interfaces. Modularity is the meta-principle behind almost every other design idea — testability, parallel development, replaceability, and maintainability all depend on it.
Abstraction — Representing essential characteristics while hiding implementation details. A Stack abstraction exposes push and pop; consumers don't need to know it is implemented as an array or a linked list. Abstraction is the cognitive lever that lets humans manage large systems.
Information Hiding (Parnas) — David Parnas's 1972 principle: each module should encapsulate a design decision and hide it from the rest of the system. The design decisions most likely to change should be hidden behind the most rigid module boundaries. The original justification for OO encapsulation.
Encapsulation — Bundling data and the operations on that data together inside a single unit (a class or module), and protecting the data from external access. Encapsulation is the OO mechanism that delivers information hiding.
Separation of Concerns (Dijkstra) — Edsger Dijkstra's principle that different concerns (UI, business logic, data access, security) should be handled in different modules. The three-tier architecture, MVC, and hexagonal architecture all operationalise this principle.
Cohesion — A measure of how strongly the elements within a single module belong together — what fraction of the module's contents serve a single, well-defined purpose. Higher cohesion is better. Stevens-Myers-Constantine (1974) ranked cohesion in seven levels from coincidental (worst) to functional (best).
Functional Cohesion — The best level of cohesion — every element of the module contributes to a single, well-defined task. A calculateInterest() function that does nothing but compute interest, or a UserRepository class that does nothing but persist users, exhibits functional cohesion.
Coincidental Cohesion — The worst level of cohesion — module elements are grouped randomly with no meaningful relationship. The classic example is a "utility" module containing a date formatter, a network sniffer, and a log rotator. Coincidental cohesion signals a module whose name cannot capture what it does.
Coupling — A measure of how strongly modules depend on each other — how much one module knows about another's internals and how easily one can change without breaking the other. Lower coupling is better. Coupling has five canonical levels from content (worst) to data (best).
Content Coupling — The worst level of coupling — one module directly accesses or modifies another's internal data or code. A GOTO into the middle of another function or a struct-field write by an outsider both exhibit content coupling. Almost always a sign of broken modularity.
Data Coupling — The best level of coupling — modules exchange only the specific data items needed, via well-defined parameters. updateAge(id, age) is data-coupled; updateUser(wholeUserObject) (when only age matters) is stamp-coupled.
Stamp Coupling — Modules pass entire data structures when only a few fields are needed. Wasteful and creates unnecessary coupling — change the struct and every caller may need to change.
Control Coupling — One module passes a flag or command that controls another's internal behaviour — process(mode) where mode selects between algorithms. Control coupling forces the caller to know the callee's internal options.
Common Coupling — Modules share global variables. Any module can read or write the shared state; reasoning about correctness becomes a whole-system problem. Common coupling is the reason "globals are evil."
Functional Independence — The combined property of high cohesion + low coupling. A functionally independent module can be developed, tested, modified, and replaced without forcing changes elsewhere. The single most important quality metric in classical structured design.
SOLID Principles — Robert Martin's five OO design principles, the modern restatement of cohesion/coupling for OO systems. Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion. Memorising these five is mandatory for any modern software engineering interview.
Single Responsibility Principle (SRP) — "A class should have only one reason to change." SRP is the OO restatement of high cohesion: a class with multiple responsibilities will be pulled by multiple change pressures.
Open-Closed Principle (OCP) — "Software entities should be open for extension but closed for modification." You should be able to add new behaviour by writing new code (a new subclass, a new strategy) without changing existing code. Achieved primarily through polymorphism.
Liskov Substitution Principle (LSP) — "Subtypes must be substitutable for their base types." If Square extends Rectangle but breaks code that expected Rectangle behaviour, the design violates LSP. Often the deciding factor for "should this be inheritance or composition?"
Interface Segregation Principle (ISP) — "No client should be forced to depend on methods it does not use." Prefer many small, focused interfaces over one large general one. ISP is the OO restatement of low coupling.
Dependency Inversion Principle (DIP) — "Depend on abstractions, not concretions." High-level modules should not depend on low-level modules; both should depend on abstractions. DIP is the principle behind dependency injection and is fundamental to testable code.
Architectural Style / Pattern — A high-level template for system structure. Layered (n-tier) stacks layers each using the one below. Client-Server separates service providers from consumers. Pipe-and-Filter chains stream-processing stages. Event-Driven routes around an event bus. Microservices is fine-grained autonomous services. MVC separates data (Model), presentation (View), and control (Controller).
Three-Tier Architecture — A specific layered architecture with presentation tier (UI), application/business tier (logic), and data tier (database). Each tier can be scaled independently and serves as a security and team-ownership boundary. The classical e-commerce layout.
MVC (Model-View-Controller) — A design pattern from Smalltalk (Trygve Reenskaug, 1979) that separates the Model (data and business logic), the View (presentation), and the Controller (input handling and routing). The foundation of most modern web frameworks (Rails, Django, Spring MVC, ASP.NET MVC).
Microservices — An architectural style where the system is composed of small, autonomous services, each owning its data and deployed independently, communicating over network APIs or message buses. Microservices buy independent scaling and faster team velocity at the cost of operational complexity. Amazon, Netflix, Uber, Flipkart all run microservices.
Top-Down vs Bottom-Up Design — Two complementary design strategies. Top-down decomposes the system starting from its overall goal, progressively refining into smaller modules. Bottom-up starts from the utilities and primitives, composing upwards into larger structures. Most real projects use a hybrid (sandwich) strategy — top-down for the architecture, bottom-up for the libraries.
Structured Design — Yourdon & Constantine's 1979 function-oriented design methodology: draw the DFD, identify transform/transaction centres, map to a structure chart of modules, refine each module. Structured design is the classical predecessor of OO design and is still the basis of much regulated/government documentation.
Structure Chart — A tree-shaped diagram showing modules as boxes and calling relationships as arrows, with annotations for the data (data couples) and control flags (control couples) flowing along each call. Structure charts are the output of structured design.
Transform / Transaction Centre — In structured design, the central module that decides what to do with input data. A transform centre transforms input into output (e.g., compile a source file). A transaction centre dispatches input to one of several handlers (e.g., a menu screen choosing between view, edit, delete).
LCOM (Lack of Cohesion of Methods) — A numerical OO cohesion metric: for each pair of methods in a class, count whether they share at least one instance variable; LCOM is the difference between pairs that don't share and pairs that do. Tools (SonarQube, JDepend) report LCOM as a refactoring signal.
---
Study deep
- Constantine's law: "A good design has high cohesion and low coupling." This is not just academic — every modern principle (microservices, SOLID, hexagonal architecture, DDD) restates this in new vocabulary.
- Functional independence is the practical measure. A module with high cohesion and low coupling has functional independence — it can be developed, tested and changed without touching the rest of the system.
- The hierarchy is qualitative. Levels of cohesion and coupling are descriptive, not numeric. The 1.0-to-7.0 ordering is for teaching — in real code there's no precise way to say "this module has 4.2 cohesion." Tools (e.g. LCOM — Lack of Cohesion of Methods) approximate the idea.
- OO design transforms the vocabulary. In object orientation, "modules" are classes. The same cohesion/coupling principles apply, plus extra principles:
- Single Responsibility (SRP) — a class should have one reason to change (cohesion)
- Open-Closed (OCP) — open for extension, closed for modification
- Liskov Substitution (LSP) — subtypes substitutable for parents
- Interface Segregation (ISP) — many small interfaces over one large
- Dependency Inversion (DIP) — depend on abstractions, not concretions
The five SOLID principles are the OO restatement of cohesion/coupling.
- Bad coupling often comes from shortcuts. "Just use a global variable" or "let me access that struct field directly" are tempting in the moment, but each shortcut increases coupling. Future maintenance cost compounds.
PYQ pattern (almost guaranteed): "What is cohesion and coupling? Explain different types of each." — Define both with the "togetherness" framing; table all 7 cohesion + 5 coupling levels with examples; end with "high cohesion + low coupling = good design."