CS 446: Software Design and Architectures
Shane McIntosh
Estimated study time: 50 minutes
Table of contents
Stakeholders and Non-Functional Properties
2.1 Who Is a System Stakeholder?
A stakeholder is broadly defined as a person with an interest or concern in something. In Rozanski and Woods’ more precise formulation: a stakeholder in the architecture of a system is an individual, team, organization, or classes thereof, having an interest in the realization of the system.
The key insight is that stakeholders extend far beyond the customer who uses the software and the developers who build it. Consider the ION light rail ticket vending machine in Waterloo — a system that, on the surface, seems to serve two groups. In reality its stakeholder community is much larger.
Customer-side stakeholders include the commuter who buys a single ticket, those paying by cash or credit, those buying a refillable card, and the Region of Waterloo that funded the entire system.
Developer-side stakeholders include software developers, user interface designers, test engineers, release engineers, operators, maintenance engineers, hardware engineers, and senior management — each with their own perspective on what the system should prioritise.
Additional stakeholders include: the person who physically installs and inspects the machines; the person who collects cash from them; network engineers who keep the machines connected; electricians who supply power; customer service operators who field complaints; and city officials who liaise with the vendor on upgrades and fixes.
The TL;DR: systems are more complex than they initially appear, and their stakeholder communities are larger than first imagined. Every stakeholder has some say in how some part of the system is designed and built — and their priorities frequently conflict.
2.2 Why Stakeholders Matter for Architecture
Different stakeholders have different concerns that may not be compatible with each other:
| Stakeholder | Typical Question |
|---|---|
| Management | Are we on schedule? |
| Developers | Who is responsible for what? |
| Sales | Can we claim it does X? |
| QA | Which team do we contact about defects? |
| DevOps | Where should this component be deployed? |
| Support | Which QA team signed off on this? |
| Maintenance | How can we add this feature? |
It is impossible to satisfy all stakeholders simultaneously, and it may be impossible to talk to all of them. At a minimum, an architect should identify who the stakeholders are and consider what their concerns might be — even if only a subset can be consulted directly. Ignoring a stakeholder (e.g., machine inspectors who need audit logs) leads to design decisions that make their work difficult or impossible.
2.3 Non-Functional Properties (NFPs)
System requirements fall into two broad categories:
- Functional Properties (FPs) — what the system is supposed to do (“the system shall do X”). These are features.
- Non-Functional Properties (NFPs) — what the system is supposed to be (“the system shall be Y”). These are the constraints under which the system delivers its functionality.
NFPs constrain the manner in which the system implements and delivers its features (Tailor et al.). Products are sold based on their FPs, but NFPs often determine whether users continue to use a product: “This program keeps crashing,” “It doesn’t work with my phone,” “It’s too slow” are all NFP failures.
Key NFP Definitions
| NFP | Description |
|---|---|
| Efficiency / Performance | How quickly the system responds to inputs and processes requests |
| Complexity | The degree to which the system’s structure is understandable |
| Scalability | Ability to handle increasing load without degrading performance |
| Heterogeneity | Ability to operate across diverse hardware, OS, or network environments |
| Portability | Ease with which the system can be moved to a new environment |
| Evolvability | Ease with which the system can be modified to accommodate new requirements |
| Readability | Ease with which the codebase can be understood by developers |
| Security | Ability to prevent unauthorised access or manipulation |
| Privacy | Ability to protect personal data |
| Usability | Ease with which users can accomplish tasks |
| Accessibility | Ability to serve users with disabilities |
| Reliability | Probability that the system performs correctly over a period of time |
| Availability | Proportion of time the system is operational |
| Robustness | Ability to handle erroneous inputs or unexpected conditions gracefully |
| Fault-tolerance | Ability to continue operating in the presence of faults |
| Survivability | Ability to recover from catastrophic failure |
| Safety | Ability to avoid states that cause physical harm or damage |
Specifying NFPs Concretely
Vague NFP statements are useless for validation. An architect must think concretely about how each NFP would be measured:
- Bad: “The system shall issue tickets quickly.”
- Good: “The time it takes for a ticket to be issued will be under 10 seconds.”
- Bad: “The system shall be usable.”
- Good: “A user shall be able to buy a ticket within 4 clicks.”
If an NFP cannot be operationalised as a measurable criterion, it is impossible to evaluate whether an architectural decision supports or inhibits it.
NFP Trade-offs
Stakeholders have different NFP priorities. The development team cares more about maintainability; customers care more about usability; QA cares more about testability. Some NFPs fundamentally conflict:
| Trade-off | Example |
|---|---|
| Functionality vs. Usability | Adding features increases cognitive load |
| Efficiency vs. Portability | Platform-specific optimisations reduce portability |
| Reusability vs. Cost | Generic components take longer to build |
Architectural decisions are often exercises in navigating these tensions — choosing which NFPs to optimise and which to trade away.
Introduction to Software Architecture
3.1 The Software Development Life Cycle
Software development is commonly divided into five phases:
- Requirements — gathering and specifying what the system must do
- Architecture — high-level structure and organisation of the system
- Design — detailed, implementation-level decisions
- Implementation — writing code
- Quality Assurance — testing, verification, validation
Undergraduate education typically emphasises phases 3–5. However, the most costly problems tend to originate in phases 1–3, long before a single line of code is written. This course focuses on phase 2 — software architecture — and the design decisions that shape the rest of development.
3.2 What Is Architecture?
The Britannica definition covers both process and product: “Architecture is both the process and the product of planning, designing, and constructing buildings or any other structures.”
The Roman architect Vitruvius (1st century AD) articulated three principles that have endured for two millennia:
- Durability — the structure must stand up robustly
- Utility — it must be useful for its intended purpose
- Beauty — it must be a source of pleasure to the senses
All three apply directly to software systems.
3.3 Why Architecture Is Needed
Not every system requires explicit architectural planning. Simple, well-understood systems — a pocket calculator, a dog house — can be built without a blueprint. But large, complex systems absolutely require it:
- The New York Stock Exchange (NYSE) processes millions of transactions per second under regulatory constraints.
- The CN Tower required careful structural engineering before a single beam was placed.
Similarly, complex software systems cannot be reliably built without an architectural blueprint.
3.4 The Software Architect
According to Tailor et al., the software architect must be:
- Broadly trained across all lifecycle phases (requirements, design, implementation, testing, deployment)
- Aesthetically minded — architecture involves choices that are both technical and elegant
- Equipped with deep domain knowledge, strong coding skills, and knowledge of the deployment platform
The architect provides three things for a project:
- Intellectual control and conceptual integrity — ensuring the system coheres as a whole
- Leverage with senior stakeholders — translating technical constraints into business language
- Team communication and management — providing a shared vocabulary and structure
3.5 The Architecture-as-Blueprint Analogy
Like a building architect, a software architect:
- Iterates on blueprints before construction begins
- Produces intermediate plans, mockups, and prototypes
- Recognises that structure induces properties: just as tall castle walls cause unexpected airflow problems, a client–server structure necessarily introduces network latency
3.6 Differences from Building Architecture
| Dimension | Building Architecture | Software Architecture |
|---|---|---|
| Age of discipline | Millennia | Decades |
| Material | Physical (stone, steel, wood) | Intangible |
| Delivery | The building itself | Deployed/released binary |
| Change expectation | Change is expensive, rare | Change is expected, routine |
| Expertise differentiation | Architects vs. builders (legal barrier) | Anyone can write code |
The intangibility of software is simultaneously its greatest strength and greatest weakness: there are no physical laws constraining a design, but this means discipline must be self-imposed.
3.7 What Architecture IS and IS NOT
Architecture IS:
- A means of communication among stakeholders
- The definition of a system’s parts and how they fit together
- Focus on aspects that are difficult to change once the system is built
Architecture IS NOT:
- About individual algorithms
- About data structures
- About development methodology
“All architecture is design, but not all design is architecture.”
Architecture sits at the intersection of three primary dimensions:
- Structure — subsystems and components
- Communication — how parts interact
- Non-functional requirements — performance, security, scalability, maintainability
3.8 The ANSI/IEEE 1471-2000 Definition
“Architecture is the fundamental organization of a system, embodied in its [subsystems], their relationships to each other and the environment, and the principles governing its design and evolution.”
3.9 Logical vs. Physical Architecture
Consider a simple course website:
Logical architecture (what the system does):
index.html → cs846.html
→ cs446.html
→ a1.html
→ a2.html
→ project.html
Physical architecture (how it runs):
- Files are hosted on one or more servers
- Clients connect via the internet using HTTP/HTTPS
The same logical structure can be realised by many different physical architectures. Separating these concerns is one of the fundamental exercises in architectural thinking.
3.10 Why Software Architecture Matters
Eoin Woods captures the stakes succinctly:
“Software architecture is the set of design decisions which, if made incorrectly, may cause your project to be cancelled.”
Architecture matters because it focuses attention on aspects that are difficult — or prohibitively expensive — to change once the system is built. Retrofitting a monolith into a microservices architecture, or adding security to a system designed without it, costs orders of magnitude more than addressing these concerns upfront.
Why Software Architecture Is Difficult
4.1 The Fundamental Challenge
Philippe Krutchen, one of the leading researchers in software architecture, frames the challenge starkly:
“The life of a software architect is a long (and sometimes painful) succession of suboptimal decisions made partly in the dark.”
Architectural decisions must be made early, when information is least complete, and they have the longest-lasting consequences.
4.2 What Makes Building Systems Hard?
Three root causes (Tailor et al.):
Young field — Software engineering has existed for roughly 60 years. Users cannot see or feel the constraints of a software system, so they tend to have very high and sometimes unrealistic expectations.
High user expectations — Because software is invisible and perceived as “just logic,” users often underestimate what is technically feasible or affordable.
Software cannot execute independently — Software relies on:
- A software platform (operating system, language runtime, libraries) that may have defects of its own
- Hardware that may be faulty (e.g., GPS-dependent applications break when positioning hardware fails)
4.3 Specific Difficulties
Four properties of software make it inherently hard to engineer:
| Property | Description |
|---|---|
| Complexity | Complexity grows non-linearly with program size. Double the code, more than double the potential for unexpected interactions. |
| Conformity | Software must conform to the hard and soft constraints of the environment in which it executes. |
| Changeability | Software is perceived to be easily modified (“we just need to change a few lines”) — this perception causes scope drift. Changeability is both a blessing and a curse. |
| Intangibility | Software is not constrained by the laws of physics. There is no natural boundary to complexity. |
4.4 Attacks on These Difficulties
How does the field respond?
- High-level languages and development tools — modern IDEs, compilers, and frameworks tame low-level complexity
- Component-based reuse — package managers and open-source ecosystems prevent reinventing the wheel
- Development strategies — incremental, evolutionary, and spiral models (Agile, Scrum) embrace change in a semi-structured way; MVP thinking gets early feedback before too much is invested
- Emphasis on architecture and design — taking a design-centric approach from the outset avoids implementing solutions that don’t scale or don’t align with user expectations
Software Architecture Styles — Introduction
5.1 A Historical Perspective on Abstraction
Programming abstractions have evolved over decades to help engineers manage growing complexity:
| Era | Abstraction |
|---|---|
| 1950s | Hardwired / machine language; then assembly language |
| 1960s | 3rd-generation languages: FORTRAN, COBOL, Algol, Pascal, Modula, C, PL/1 |
| 1960s onward | 3.5GL functional languages: Scheme, Scala |
| Late 1960s | Typed variables and user-defined types |
| Early 1970s | Modules with interfaces / information hiding (triggered by the 1968 “software crisis”) |
| Mid-1970s | Abstract Data Types (ADTs) and Object-Oriented programming |
| 1990s | OO Design Patterns and refactoring |
| 1990s | Software Architecture (though the ideas existed since the 1960s) |
5.2 The Abstraction Pyramid
Three levels of abstraction in software:
┌────────────────────────────────────────────────────┐
│ SW Architecture │ ← Components/modules + interdependencies
│ (the "big boxes") │
├────────────────────────────────────────────────────┤
│ Design Patterns │ ← Class-level recurring solutions
├────────────────────────────────────────────────────┤
│ Algorithms │ ← Single method / function level
└────────────────────────────────────────────────────┘
As size and complexity grow, design goes beyond algorithms and data structures. Architecture addresses the “big boxes” — the major components and how they are connected.
5.3 Types of Software Architectures
- Reference Architecture — a general architecture for an entire application domain. Example: a compiler pipeline (pre-process → compile → link) is a reference architecture for all compilers.
- Product Line Architecture (PLA) — an architecture for a family of related products. Example: successive entries in a game series share a PLA.
5.4 Software Architecture Issues
Key concerns at the architectural level:
- Organisation and global control structure
- Communication, synchronisation, and data-access protocols
- Assignment of functionality to design elements
- Physical distribution of components
- Selection among design alternatives
5.5 State of Practice
The field lacks a single well-defined terminology or notation. Practitioners use:
- Informal rules of thumb and idiomatic patterns — tacit knowledge, ad hoc conventions
- Formal industry standards — more rigorous but less flexible
Real-world systems often mix multiple styles — the so-called “Toaster” Model: different sub-collections within the same system may follow different architectural styles.
5.6 Building Architecture as a Parallel
Three analogous aspects of building architecture clarify software architecture:
- Multiple Views — a building has skeleton frame views, electrical wiring views, plumbing views, etc. Software has analogous architectural views.
- Architectural Styles — buildings have Classical, Romanesque, Gothic, Modernist, etc. Software has analogous named styles.
- Materials — one does not build a skyscraper from wooden posts and beams. In software, the choice of language, framework, and platform constrains what is feasible.
5.7 What Is a Software Architectural Style?
An Architectural Style defines a family of systems in terms of a pattern of structural organisation. It determines:
- The vocabulary of components and connectors that can be used in instances of that style
- A set of constraints on how they can be combined
5.8 Why Use Architectural Styles?
Three reasons:
- Easy communication — named styles give stakeholders a shared vocabulary. Rather than describing the entire structure from scratch, saying “this is a client–server system” communicates volumes.
- Documentation of early design decisions — capturing the chosen style early preserves the reasoning behind major structural choices.
- Reuse and transfer of qualities — proven styles carry known quality attributes. Choosing a pipe-and-filter style immediately imports its known advantages (parallelisability, reusability of filters) and disadvantages (no shared state).
Roy T. Fielding (co-author of HTTP and inventor of REST) captured the right attitude toward styles:
“Architectural styles are named so we can easily discuss & contrast them, not so they can be carried on banners or elevated to purity.”
Use styles to communicate the general idea. Be ready to break them when needed — but be aware of the consequences.
5.9 Describing an Architectural Style for a System
The architecture of a specific system is a collection of:
- Computational components (nodes in a graph): e.g., Procedures, Modules, Processes, Tools, Databases
- Connectors (edges in a graph): e.g., procedure calls, event broadcasts, database queries, pipes
Together these form a topology.
5.10 Determining an Architectural Style
To fully understand and evaluate a given style, answer six questions:
- What is the structural pattern? (components, connectors, constraints)
- What is the underlying computational model?
- What are the essential invariants of the style?
- What are common examples of its use?
- What are the advantages and disadvantages of using this style?
- What are common specialisations of this style?
5.11 Architectural Styles Covered in This Course
| Category | Styles |
|---|---|
| Pipe-and-filter | Pipelines, Batch Sequential |
| Data-centred | Repository, Blackboard |
| Implicit invocation | Publish/Subscribe, Event-Based |
| Layered | — |
| Client–server | — |
| Process-control | — |
Architectural Views and Decomposition
6.1 Software Architecture: A Working Definition
Software architecture = the set of principal design decisions about a system.
Every system has an architecture, whether explicitly documented or not. The architect’s job is to make those decisions deliberately and to record them.
6.2 Core Vocabulary
| Term | Definition |
|---|---|
| Subsystem | A meaningful grouping of components with a defined interface |
| Component | A computational unit with an interface; can be a module, process, class, service |
| Connector | A mechanism for communication between components (method call, pipe, event bus, HTTP request) |
| Configuration (Topology) | The arrangement of components and connectors; the graph structure |
6.3 Topological Goals
- Minimise coupling — components should depend on as few other components as possible
- Maximise cohesion — elements within a component should be strongly related and focused on a single concern
6.4 Abstraction
Two types of abstraction are fundamental:
- Control abstraction — structured programming (procedures, loops, conditions) hides the details of control flow
- Data abstraction — ADTs and OOP hide the internal representation of data behind an interface
Architectural decomposition is an application of data abstraction at the system level.
6.5 Decomposition
Decomposition is top-down abstraction: breaking a large problem into smaller, more manageable sub-problems.
Criteria for decomposing a system:
- Team characteristics — match modules to team expertise and organisational structure
- Application domain — decompose along natural boundaries in the problem domain
- Parallelisation — identify independent sub-problems that can be developed concurrently
Goal: typical cases should be simple to handle; exceptional cases should at least be possible.
Conway’s Law
“The structure of a software system reflects the structure of the organisation that built it.”
Organisations communicate along certain channels. The software they produce will tend to have interfaces (or seams) in the same places. This is not a critique — it is an empirical observation with design implications: if you want a modular architecture, organise your teams modularly.
When Decomposition Doesn’t Work
Decomposition is powerful but not universal:
- Works well for systems like a restaurant menu (each dish is independent)
- Does not always work for creative artefacts (characters in a play cannot write in isolation — their dialogue depends on each other)
- Sometimes impossible: complex, deeply coupled, or fundamentally atomic problems
6.6 Architectural Representations
An architecture that cannot be communicated has no value. Representations facilitate technical communication.
Three properties of representations:
| Property | Definition |
|---|---|
| Ambiguity | Open to multiple valid interpretations |
| Accuracy | Correct within acceptable tolerances |
| Precision | Consistent, but not necessarily accurate (systematic but may be wrong) |
A good architectural representation is unambiguous, accurate, and precise.
6.7 Prescriptive vs. Descriptive Architecture
| Type | Also called | Meaning |
|---|---|---|
| Prescriptive | As-conceived (a priori) | The intended architecture; the blueprint |
| Descriptive | As-implemented (a posteriori) | The actual architecture extracted from the running system |
In healthy projects these are the same. In unhealthy projects they diverge.
6.8 Architectural Degradation
Two forms of divergence between prescriptive and descriptive architecture:
| Form | Definition |
|---|---|
| Architectural Drift | Changes to the implementation that are not captured in the prescriptive architecture, but do not violate it |
| Architectural Erosion | Changes that violate the prescriptive architecture |
Common causes:
- Release pressure (shipping fast, not updating docs)
- Absence of a prescriptive architecture in the first place
- Local optimisations by individual developers that make global sense only to them
Erosion is the more dangerous of the two: it means the system no longer behaves as designed.
6.9 Architectural Views
A single architectural model of a large system would be overwhelming. Views are projections of the architecture that focus on a subset of elements and their relationships:
- Different views emphasise different concerns (static structure, runtime behaviour, deployment)
- Views overlap — the same component may appear in multiple views
- Maintaining consistency between views is a significant challenge
UML for Software Architecture
UML (Unified Modelling Language) provides five diagram types commonly used for architectural views.
Martin Fowler identified two “religions” of UML:
- UML as Blueprint — forward engineering; the diagram drives the code
- UML as Programming Language — the diagram is the executable specification
In practice, UML is most useful as a communication and documentation tool.
7.1 Class Diagrams (Design-Level View)
Class diagrams show the static structure of a design: classes, their attributes, methods, and relationships.
Relationships:
| Relationship | UML Symbol | Meaning |
|---|---|---|
| Dependency | Dashed arrow | One class uses another (weak coupling) |
| Generalization | Solid arrow with hollow triangle | Inheritance / “is-a” |
| Association | Solid line | “has-a” or “uses-a” structural relationship |
| Aggregation | Solid line with hollow diamond | Weak whole–part (“has-a”, part can exist independently) |
| Composition | Solid line with filled diamond | Strong whole–part (part cannot exist without the whole) |
Association details:
- Multiplicity —
1,0..1,*,1..*, etc., placed at each end of the association line - Role names — labels at the ends describing the role of each class in the relationship
- Association classes — a class attached to an association line, representing attributes of the relationship itself
- Self-association — a class associated with itself (e.g., an Employee manages other Employees)
Other notations:
<<interface>>stereotype for interfaces<<enumeration>>for enumerations<<exception>>for exception classes- Packages — rectangles with a tab, used to group related classes
7.2 Component Diagrams (Physical Static Implementation View)
Component diagrams show the physical organisation of software components and their dependencies.
Notation:
- Page-shaped blocks — shared libraries or compiled modules
- Circles — connectors / interfaces exposed by a component
- Arrows — dependency relationships (A depends on B)
Example: A collision detection library:
PATH.DLL → COLLISION.DLL → DRIVER.DLL
↑ (conditional, via IDRIVE interface)
7.3 Deployment Diagrams (Static Deployment View)
Deployment diagrams show the run-time processing nodes and how software components are assigned to them.
Notation:
- Block shapes (cubes) — processing nodes (physical machines or containers)
- Circles — interfaces
- Realization dependency — interface fulfilled by a component
- Usage dependency — component uses an interface
Examples:
DICTIONARY → SPELL-CHECK / SYNONYMS(interfaces on the DICTIONARY node)ATM-GUI → UPDATE → TRANSACTIONS- A client–server system with
SERVER:HOSTMACHINEandCLIENTMACHINE:PCnodes
7.4 Use Case Diagrams
Use case diagrams capture the functional requirements from the actor’s perspective.
Notation:
- Ellipse with name — a use case
- Stick figure — an actor
- Bidirectional association — actor participates in use case
- Actor generalisation — e.g.,
Student → Person(Student is a Person) - Primary actors on the left; secondary actors (e.g., external systems) on the right
Example:
Student ——— Register For Courses ——— Billing System
Registrar
7.5 State Machine Diagrams
State machine diagrams describe the dynamic behaviour of objects over time, modelling an object’s lifecycle.
Notation:
- Filled circle — start state (exactly one per machine)
- Filled circle with ring — final state (one or more)
- Rounded rectangle — simple state
- Rounded rectangle with dashed horizontal divider — concurrent composite state (regions execute in parallel)
- Nested rounded rectangles — sequential composite state
Example (course registration flow):
● → Unscheduled → Downloading → Selecting → Verifying
→ Checking Schedule → Scheduled → ◎
7.6 Sequence Diagrams
Sequence diagrams show time-ordered interactions among objects.
Notation:
- Objects on the x-axis; time on the y-axis (flows downward)
- Object lifeline — dashed line descending from the object box
- Messages — horizontal arrows (solid arrowhead = synchronous call; non-solid arrowhead = return)
- Conditions — expressed in
[guard]before an arrow - Return values — non-solid arrowhead, typically unlabelled
- Iteration —
*marker or*[i=1..N] - Self-delegation — an object sending a message to itself
Example (order management):
Order Entry Window → Order → * Order Line → Stock Item → (self) → Reorder Item
Delivery Item
7.7 Collaboration Diagrams
Collaboration diagrams (also called communication diagrams) emphasise object relationships rather than time order.
- Objects are identified as
objectname : classname - Messages are labelled with sequence numbers instead of positioned by time
- Semantically equivalent to sequence diagrams (both derived from the same UML meta-model)
7.8 Activity Diagrams
Activity diagrams are essentially flowcharts for modelling workflows.
“An activity is an ongoing non-atomic execution within a state machine.”
Notation:
- Solid horizontal bars — fork (one flow splitting into parallel flows) or join (parallel flows converging)
- Synchronisation conditions — placed at join bars
- Activities connected by directed arrows
Example (order management):
Receive Order → fork → Authorize Payment → join [payment authorised
→ Check Line Items → AND items available] → Dispatch Order
Object-Oriented Design Patterns — Introduction
8.1 What Are Design Patterns?
A design pattern is a reusable, named solution to a commonly occurring problem in a given context. Patterns capture the collective experience of practitioners — they are not invented but discovered by observing recurring good designs.
The canonical reference is the Gang of Four (GoF) book (Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, Vlissides), which catalogues 23 patterns in three categories:
| Category | Focus | Examples |
|---|---|---|
| Creational | Object creation | Singleton, Factory Method, Abstract Factory, Builder, Prototype |
| Structural | Object composition | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy |
| Behavioural | Communication/responsibility | Observer, Strategy, Command, Iterator, Template Method, etc. |
8.2 Design Goals
- High cohesion — each class has a single, focused responsibility
- Low coupling — classes depend on as few other classes as possible
- YAGNI principle — “You Ain’t Gonna Need It”: do not add functionality speculatively; add it when needed
8.3 Code Smells
Code smells are symptoms of poor design that may warrant refactoring:
| Smell | Description |
|---|---|
| Code Duplication | The same logic appears in multiple places |
| Multiple-Personality Class | A class trying to do too many unrelated things |
| Blob / God Object | One class that knows and does everything |
| Data Class | A class with only fields and getters/setters, no behaviour |
| Data Clump | Groups of data that always appear together but are not encapsulated |
| Feature Envy | A method that is more interested in the data of another class than its own |
| Tradition Breaker | A subclass that overrides most parent methods, suggesting the hierarchy is wrong |
Structural Design Patterns
9.1 The Adapter Pattern
Category: Structural
Intent: Convert the interface of a class into another interface that clients expect. Adapter lets classes work together that could not otherwise because of incompatible interfaces.
Motivation: You have an existing class (the Adaptee) with a useful implementation but the wrong interface. A Target interface is what clients expect. The Adapter sits between them.
Analogies:
- A power plug adapter converts a two-pin plug to a three-pin socket — the appliance (adaptee) still works, but through a different interface.
- Translating from British English to American English: the content is the same, but the vocabulary (interface) differs.
Two forms:
Class Adapter (uses multiple inheritance):
Adapter extends Adaptee implements Target+request()in Adapter callsspecificRequest()inherited from Adaptee- Works only with a single concrete Adaptee; can override Adaptee behaviour
Object Adapter (uses composition):
Adapter implements Target, holds a reference to Adaptee+request()delegates toadaptee.specificRequest()- More flexible: works with Adaptee and all its subclasses
Participants:
- Target — the interface clients use
- Adaptee — the existing class with an incompatible interface
- Adapter — translates Target requests into Adaptee calls
- Client — collaborates with objects through the Target interface
When to use:
- Reusing an existing class whose interface doesn’t match what is needed
- Creating a reusable class that cooperates with classes that don’t share a compatible interface
- Adapting several existing subclasses by wrapping a parent class (object adapter)
Consequences:
- Class Adapter: more compact, but only works with concrete Adaptee
- Object Adapter: more flexible, adds an indirection level
9.2 The Bridge Pattern
Category: Structural
Intent: Decouple an abstraction from its implementation so the two can vary independently.
Motivation: Inheritance couples an abstraction permanently to its implementation. If you want to switch implementations (e.g., render a UI on different platforms), inheritance forces a proliferation of subclasses. Bridge replaces that inheritance relationship with a composition relationship.
Key insight: Instead of extending a class to add a new implementation, Bridge holds an interface reference to an independent implementation hierarchy.
Structure:
Abstraction (holds reference to Implementor interface)
↓ uses
Implementor <<interface>>
↙ ↘
ConcreteImplementorA ConcreteImplementorB
Refined Abstraction is a subclass of Abstraction that adds behaviour while still delegating implementation-specific work to the Implementor.
Example (Window / UI toolkit):
Window(abstraction) hasIconWindowandTransientWindowsubclassesWindowImp(implementor) hasXWindowImpandPMWindowImpsubclasses- Any combination of window type and platform can be assembled at runtime
When to use:
- Want to avoid a permanent binding between abstraction and implementation
- Both abstraction and implementation should be extensible through subclassing
- Changes in implementation should have no impact on clients
- Want to hide implementation details completely
Consequences:
- Decouples interface and implementation
- Improves extensibility
- Hides implementation details from clients
9.3 The Composite Pattern
Category: Structural
Intent: Compose objects into tree structures to represent part–whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Motivation: Drawing programs mix individual shapes (Line, Circle) with groups of shapes. Client code should not need to distinguish between “I’m operating on a single shape” vs. “I’m operating on a group of shapes.”
Structure:
Component <<interface/abstract>>: +draw(), +add(Component), +remove(Component)
↙ ↘
Leaf Composite (has children: List<Component>)
(no children) +draw() iterates over children and calls their draw()
Key property: Both Leaf and Composite implement the same Component interface. A Composite’s operation simply delegates to each of its children — which may themselves be Composite instances. This gives recursive tree behaviour.
Example (drawing editor):
Picture= Composite containingLine,Circle,Rectangle(Leaves) and otherPictures (Composites)
Participants:
- Component — declares the common interface; implements default behaviour if appropriate; declares interface for accessing/managing children
- Leaf — has no children; defines behaviour for primitive objects
- Composite — stores children, implements child-related operations, and implements Component operations by delegating to children
- Client — manipulates objects through the Component interface
When to use:
- Representing part–whole hierarchies
- Clients should ignore differences between individual objects and compositions
Consequences:
- Makes client code simple (treats leaves and composites uniformly)
- Easy to add new kinds of components
- Can make design overly general (hard to restrict what components can contain what)
9.4 The Decorator Pattern
Category: Structural
Intent: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Motivation: You want to add features to individual objects, not to the entire class. Subclassing can lead to a combinatorial explosion of classes for every combination of features.
Key idea: A Decorator wraps the component it decorates. It has the same interface as the component (so clients are unaffected) and adds behaviour before or after delegating to the wrapped component.
Structure:
Component <<interface>>: +draw()
↙ ↘
ConcreteComponent Decorator (holds Component reference)
↙ ↘
ConcreteDecoratorA ConcreteDecoratorB
Decorator’s draw() method:
// Before behaviour (optional)
component.draw(); // delegate to wrapped component
// After behaviour (optional)
Example (text with visual enhancements):
TextView(ConcreteComponent)BorderDecoratoradds a border around whatever it wrapsScrollDecoratoradds scroll bars to whatever it wrapsScrollDecorator(BorderDecorator(TextView))produces a scrollable, bordered text view — no subclass explosion needed
When to use:
- Add responsibilities to objects without affecting other objects
- Extend functionality when subclassing is impractical (class explosion or not desired)
- Withdraw responsibilities dynamically
Consequences:
- More flexible than static inheritance
- Avoids feature-laden classes high in the hierarchy
- Decorated component and its decorator are not identical objects (identity may matter)
- Many small objects (each decorator is a separate object) can be confusing
9.5 The Proxy Pattern
Category: Structural
Intent: Provide a surrogate or placeholder for another object to control access to it.
Motivation: Sometimes a client should not (or cannot) directly reference an object. A Proxy acts as a stand-in, forwarding requests to the real object when appropriate.
Three types of proxy:
| Type | Purpose | Example |
|---|---|---|
| Remote Proxy | Represents an object in a different address space (another machine) | Java RMI stub |
| Virtual Proxy | Creates expensive objects on demand | Image placeholder that loads the full image only when displayed |
| Protection Proxy | Controls access based on permissions | A proxy that checks whether a client has the right to call a method |
Structure:
Subject <<interface>>: +request()
↙ ↘
RealSubject Proxy (holds reference to RealSubject)
+request() +request() → [optionally] realSubject.request()
Participants:
- Subject — common interface for Proxy and RealSubject, so Proxy can substitute
- RealSubject — the real object being proxied
- Proxy — keeps a reference to RealSubject; controls access; may defer creation
When to use:
- Lazy initialisation (virtual proxy)
- Remote access (remote proxy)
- Access control (protection proxy)
- Logging or caching wrappers
9.6 The Facade Pattern
Category: Structural
Intent: Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
Motivation:
- Structuring a system into subsystems reduces complexity
- A common design goal is to minimise communication and dependencies between subsystems
- Use a facade object to provide a single, simplified entry point to a subsystem
Structure:
Client Classes
↓ all access through
Facade
↓ delegates to
[Subsystem Classes — complex internal relationships hidden from clients]
Example (compiler programming environment):
Compiler.Compile()is the Facade (single, simple interface)- Subsystem classes:
Scanner,Token,Parser,ProgNodeBuilder,ProgNode,CodeGenerator,RISCCG,StackMachineCG,StatementNode,ExpressionNode,VariableNode - Clients only need to call
Compile()— they need not know which subsystem classes handle which parts
Participants:
- Facade — knows which subsystem classes are responsible for a request; delegates client requests to the appropriate subsystem objects
- Subsystem Classes — implement subsystem functionality; handle work assigned by the Facade; have no knowledge of the Facade (no reference back to it)
When to use:
- To provide a simple interface to a complex subsystem
- To decouple clients from implementation classes
- To define a single entry point to a layered subsystem
Adapter vs. Facade:
| Pattern | Scope | Purpose |
|---|---|---|
| Adapter | Single class | Adds a “human face” to one grungy abstraction; does not change the adaptee |
| Facade | Whole subsystem | Puts a human face on an entire system; usually requires modifying core code to route all access through the facade |
Architectural Styles — Pipe-and-Filter, Data-Centred
10.1 Pipe-and-Filter Style
Overview: Components (called filters) transform data streams. Data flows through pipes connecting filters. Each filter is independent — it reads from its input pipe, processes data, and writes to its output pipe.
Components:
- Filter — a data transformer; reads input, processes, produces output
- Active filter: pulls data from input, pushes to output (runs in its own thread)
- Passive filter: called by the pipeline; either push-driven or pull-driven
Connectors:
- Pipe — a unidirectional data channel; can be synchronous (blocking) or buffered
Topology:
Input → [Filter A] → Pipe → [Filter B] → Pipe → [Filter C] → Output
Invariants:
- Filters are independent — they share no state with other filters
- Filters do not know the identity of their upstream or downstream neighbours
- Data is transformed as it flows through the sequence
Examples:
- Unix shell pipelines:
cat file.txt | grep "error" | sort | uniq -c - Compiler: lexer → parser → semantic analyser → code generator → optimiser
- Signal processing: microphone → noise filter → amplifier → compressor → speaker
Advantages:
- Easy to understand as a simple sequence
- Supports reuse — each filter can be reused in a different pipeline
- Easy to maintain and evolve — add, remove, or replace filters
- Supports concurrent execution — filters can run in parallel
- Allows specialised filters to analyse data quality
Disadvantages:
- Not suitable for interactive applications (filters process whole data streams)
- Shared-state interactions are awkward (filters cannot share data structures)
- Data format must be agreed on by adjacent filters
- Overhead of parsing/unparsing if filters use different internal representations
Variants:
- Batch Sequential — the “degenerate” form where each filter processes the entire dataset before the next begins (no true streaming); classic mainframe ETL pipelines
10.2 Data-Centred Style: Repository
Overview: A large, passive central data store (the repository) is shared by multiple independent computational components. Components read from and write to the repository.
Components:
- Repository (Data Store) — the central passive data structure
- Computational components — active agents that query and update the repository
Connectors:
- Direct procedure calls or queries (SQL, API)
- The repository schema is the shared communication medium
Topology:
[Comp A] ←→
[Comp B] ←→ [ Repository / Central Data Store ]
[Comp C] ←→
Invariants:
- All data shared between components flows through the repository
- Components do not communicate directly with each other
Examples:
- Relational database systems (all components query the same DB)
- Compilers with a symbol table as the shared repository
- Information retrieval systems
Advantages:
- Efficient storage of large data — each component does not need its own copy
- Sharing is easy — the schema is published; all components know how to access data
- Centralised management — supports backup, security, and concurrency control in one place
Disadvantages:
- Must agree on a data model a priori — all components are coupled to the schema
- Difficult to distribute — if multiple copies exist, they must stay in sync
- Data evolution is expensive — changing the central schema requires updating all components
- Single point of security failure — if the repository is compromised, the whole system is compromised
10.3 Data-Centred Style: Blackboard
Overview: A variant of the Repository style where the data store is active — it notifies computational components (called knowledge sources) when relevant data changes. Used for problems where the solution strategy is not known in advance.
Components:
- Blackboard — the shared data structure; triggers knowledge sources
- Knowledge Sources — specialist components that each solve a part of the problem; each monitors the blackboard for data relevant to it
Connectors:
- Active notification (the blackboard triggers knowledge sources, unlike the passive repository)
- A control component may manage the order in which knowledge sources are activated
Topology:
[KS1] [Control]
[KS2] ←→ [Blackboard] ←→ inspects/drives
[KS3]
Examples:
- Speech recognition systems (multiple specialist analysers — acoustic, phonetic, lexical, syntactic, semantic — each contribute partial interpretations)
- AI planning systems
- Complex event processing
Key difference from Repository: In a repository, components decide when to access shared data. In a blackboard, the blackboard (or a control component) decides when to activate knowledge sources based on the current state of the data.
Architectural Styles — Implicit Invocation, Layered, Client–Server, Process-Control
11.1 Implicit Invocation Style
Overview: Components do not invoke each other directly. Instead, a component announces an event or broadcasts data. Other components that have registered interest respond — the announcer has no knowledge of who will respond, or even whether anyone will.
Core idea: Suitable for applications where creators of data do not need to know who will use the data or how many consumers there are.
Components:
- Data generators (publishers/emitters) — produce and broadcast data
- Data consumers (subscribers/event handlers) — wait for and process data produced by generators
Connectors:
- Procedure calls — used in publish-subscribe to connect subscriber to publisher at registration time
- Event bus — the shared communication medium in event-based style
Two variants:
Variant 1: Publish-Subscribe
- Subscribers register to receive specific messages from publishers
- Publishers maintain a subscription list and broadcast messages to all registered subscribers
- Subscribers connect to publishers directly (or through a network)
Topology:
- One publisher (e.g., game server)
- Multiple independent subscribers connected via remote procedure calls
- Publisher broadcasts to all subscribers simultaneously
Examples:
- Twitter — a user (publisher) posts a tweet; all followers (subscribers) receive it
- Environment Canada — publishes weather data for each location; news services subscribe to that data
Variant 2: Event-Based
- Components asynchronously emit and receive events over a shared event bus
- Components do not directly talk to each other — all communication goes through the bus
- Any component can both emit and consume events
- Data on the bus is ephemeral — it expires once it has passed through the entire bus
Topology:
- All components connected to the same event bus
- Spacecraft, Clock, Game Logic, GUI — each connected to a centralised event bus
- The bus routes events; emitters do not know which consumers will respond
Key distinction between variants: In event-based, each component can both emit and consume events. In publish-subscribe, a publisher may only publish data (though it may also subscribe to other publishers).
Examples:
- Programming environments — the debugger stops at a breakpoint and emits an event; the editor (a separate component) responds by scrolling to the appropriate source line and highlighting it
- GUI frameworks (button click → event → multiple listeners respond)
Advantages:
- Strong support for reuse — any component can be added simply by registering for events; existing components need not change
- Eases system evolution — components can be replaced without affecting others (as long as they produce/consume the same events)
- Efficient dissemination of one-way information (publish-subscribe) — information is broadcast to all interested parties simultaneously
Disadvantages:
- When a component announces an event:
- It has no idea what other components will respond
- It cannot rely on the order in which responses are invoked
- It cannot know when responses are finished
- Special protocols needed when number of subscribers is very large (publish-subscribe)
11.2 Layered Style
Overview: The system is organised into a hierarchy of layers. Each layer provides services to the layer above and uses services from the layer below. Direct access between non-adjacent layers is prohibited.
Components:
- Layers — each layer is a coherent grouping of functionality with a well-defined interface
Connectors:
- Procedure calls — each layer calls the interface of the layer immediately below
Topology:
Layer N (highest — closest to user)
↕
Layer N-1
↕
...
↕
Layer 1 (lowest — closest to hardware/platform)
Invariants:
- Layer i may only use services from layer i-1
- Layer i may only provide services to layer i+1
- No skip-layer access (unless explicitly permitted in a “relaxed layered” variant)
Examples:
- ISO/OSI Network Model — 7 layers from Physical to Application
- Operating System — hardware → kernel → system calls → user applications
Advantages:
- Supports incremental development — build lower layers first, then build higher layers on top
- Separation of concerns — each layer has a clear, bounded responsibility
- Enhances portability — changing the lower layers (e.g., swapping OS) only affects the layer immediately above
Disadvantages:
- Not all systems fit naturally into a hierarchy
- Performance penalty — each layer adds overhead of translation/marshalling
- Difficult to determine where to put a layer that spans multiple concerns
11.3 Client–Server Style
Overview: A server provides services; clients request those services. The server does not know about clients; clients know about the server.
Components:
- Client — initiates requests; has a user interface
- Server — provides services; waits for and responds to requests; typically persistent and always-on
Connectors:
- Remote Procedure Call (RPC) or message passing (HTTP, sockets)
Topology:
[Client 1] ──┐
[Client 2] ──┤ → [Server]
[Client 3] ──┘
Invariants:
- Communication is initiated by the client
- The server does not know the identity of clients (in the general case)
- Roles are asymmetric
Advantages:
- Clear separation of concerns
- Server is easily scaled or upgraded independently
- Clients are lightweight
Disadvantages:
- Server can be a bottleneck / single point of failure
- Network latency adds overhead
- Managing distributed state is complex
11.3a Model-View-Controller Style
MVC is a specialisation of the implicit-invocation/observer pattern applied to interactive systems. It separates the application’s data model from its presentation and from user-input handling.
11.4 Process-Control Style
Overview: Designed for real-time, embedded systems where software must continuously monitor and manipulate a physical process. A feedback loop maintains a controlled variable at a desired set point.
Components:
- Process Definition — the physical or logical process being controlled; includes mechanisms for manipulating process variables
- Control Algorithm — decides how to manipulate process variables to maintain the desired state
Connectors (data flow relations):
- Process Variables:
- Controlled variable — the variable whose value the system is intended to control (e.g., speed)
- Input variable — measures an environmental input to the process (e.g., load on engine)
- Manipulated variable — the variable the controller can change to affect the controlled variable (e.g., throttle setting)
- Set Point — the desired value for the controlled variable
- Sensors — obtain the current values of process variables
Topology (closed-loop / feedback control):
set point → [Controller] → changes to manipulated variables → [Process] → controlled variable
↑ |
└───────────────── sensor for controlled variable ─────────────┘
(feedback loop)
In a closed-loop system, the sensor feeds back the actual controlled variable so the controller can compare it to the set point and make corrections.
Variant: Open-Loop Control System
In an open-loop system, the feedback path is absent. Information about the process variable is not used to adjust the system. The controller operates on a fixed input without observing the outcome.
set point → [Controller] → changes to manipulated variables → [Process] → controlled variable
(no feedback)
Examples:
- Automobile Anti-Lock Brakes (ABS) — sensors detect wheel lock; controller modulates brake pressure
- Nuclear Power Plants — sensors monitor reactor temperature and neutron flux; control rods are adjusted
- Automobile Cruise Control — desired speed (set point) + wheel rotation pulses (sensor) → controller → throttle setting (manipulated variable) → engine → wheel rotation (controlled variable) — feedback loop maintained
Cruise control diagram:
desired speed → [Controller] → throttle setting → [Engine] → wheel rotation
↑ |
└─────────────── pulses from wheel ─────────────┘
Project Scheduling
12.1 Why Project Scheduling?
Software projects must be delivered on time and within budget. Project scheduling provides a structured plan to allocate work, sequence activities, assign resources, and track progress.
12.2 Work Breakdown Structure (WBS)
A Work Breakdown Structure decomposes the total project into manageable units:
- The project is broken into deliverables
- Each deliverable is broken into activities (or work packages)
- Activities are the atomic units that can be estimated, assigned, and tracked
12.3 Key Scheduling Concepts
| Term | Definition |
|---|---|
| Milestone | A significant point in the project; typically marks completion of a deliverable or phase |
| Activity | A defined unit of work with a start, end, duration, and resource requirements |
| Duration | Calendar time required to complete an activity |
| Effort | Person-hours required (duration × number of people assigned) |
| Dependency | A constraint that one activity must start/finish before another can start/finish |
Dependency types:
| Type | Abbreviation | Meaning |
|---|---|---|
| Finish-to-Start | FS | B cannot start until A finishes (most common) |
| Start-to-Start | SS | B cannot start until A starts |
| Finish-to-Finish | FF | B cannot finish until A finishes |
| Start-to-Finish | SF | B cannot finish until A starts (rare) |
12.4 Critical Path Method (CPM)
The Critical Path is the longest sequence of dependent activities through the project. It determines the minimum project duration.
Key terms:
| Term | Definition |
|---|---|
| Earliest Start Time (ES) | The earliest time an activity can begin, given all predecessors are complete |
| Earliest Finish Time (EF) | ES + Duration |
| Latest Finish Time (LF) | The latest time an activity can finish without delaying the project |
| Latest Start Time (LS) | LF − Duration |
| Slack (Float) | LS − ES (or LF − EF): how much an activity can be delayed without delaying the project |
Activities on the critical path have zero slack — any delay in them delays the entire project.
Algorithm:
- Forward pass — compute ES and EF for all activities (left to right)
- Backward pass — compute LF and LS for all activities (right to left)
- Identify critical path — activities with slack = 0
12.5 Gantt Charts
A Gantt chart is a horizontal bar chart representing the project schedule:
- Y-axis — activities (one per row)
- X-axis — calendar time (divided into equal units: days, weeks, months)
- Bars — the duration of each activity
- Dependencies — arrows between bars
- Team member names — written inside the bars to show who is working on what
- Resource availability — Gantt charts can also show limited resources (e.g., a stove that cannot prepare two dishes simultaneously; a developer who cannot work two tasks simultaneously)
Practical advice for course projects:
- Use a spreadsheet (Excel, Google Sheets)
- Each column = equal unit of time; each row = one activity
- Write team members’ names in the rectangles
- Ensure no team member is assigned to two activities at the same time
- Clearly indicate dependencies between tasks
12.6 Representing Resource Availability
In software projects, critical resources include:
- Staff — developers, QA personnel, site reliability engineers (SREs)
- Computing resources — servers, build machines
- Financial resources — budget, cashflow constraints
Gantt charts can be extended to show resource utilisation and identify over-allocation.
12.7 Project Crashing
Project crashing describes schedule compression techniques used when a project must be shortened without changing its scope.
Important: “Project crashing” has nothing to do with software crashes or failures.
Key terms:
| Term | Definition |
|---|---|
| Crashing | Reducing project time by expending additional resources (e.g., hiring more staff, acquiring more servers) |
| Crash time | The amount of time saved by applying crash resources to an activity |
| Crash cost | The cost of the additional resources spent to achieve the crash time |
| Goal | Maximise crash time while minimising crash cost (a multi-objective optimisation) |
Crash cost per week = (Crash cost − Normal cost) / (Normal time − Crash time)
Example calculation: For Activity 1 with Normal time 12 weeks, Crash time 7 weeks, Normal cost $3,000, Crash cost $5,000:
- Total allowable crash time = 12 − 7 = 5 weeks
- Crash cost per week = ($5,000 − $3,000) / 5 = $400/week
12.8 Time–Cost Tradeoff
There is a fundamental tradeoff when crashing a project:
- Direct costs decrease as project duration increases (less crashing needed)
- Indirect costs (overhead, penalties for late delivery) increase as project duration increases
Optimal project time = the point where total project cost (direct + indirect) is minimised. Graphically, this is the bottom of the U-shaped total cost curve.
12.9 Caveat: The Mythical Man-Month
Fred Brooks, in The Mythical Man-Month, observed that adding more human capital does not always accelerate a software project. This is especially true for projects that are already late:
Adding staff to a late software project often makes it even later.
Reasons:
- Ramp-up costs — new developers need time to learn the codebase
- Communication costs — each new developer added multiplies the number of communication channels
Implication: crashing may not always be possible for software projects. Prefer crashing specific, well-defined activities rather than the whole project.
Software Cost Estimation
13.1 Why Cost Estimation?
Before a project begins, stakeholders need to know: how much will this cost? How long will it take? Accurate estimation enables informed decisions about whether to proceed, how to staff the project, and how to bid on contracts.
13.2 Challenges of Software Cost Estimation
Software cost estimation is notoriously difficult because:
- Requirements may be incomplete or volatile
- No two projects are identical
- The invisibility of software makes progress hard to measure
- Individual developer productivity varies widely
13.3 Algorithmic Cost Models
Algorithmic models use a formula with project attributes as inputs to produce effort or cost estimates.
General form:
Effort = A × Size^B × M
Where:
Sizeis a measure of the software (e.g., lines of code, function points)AandBare calibration constants derived from historical dataMis a multiplier based on project attributes (complexity, team experience, etc.)
COCOMO II
COCOMO II (Constructive Cost Model II) is one of the most widely used algorithmic models.
- Developed at the Center for Software Engineering, USC
- Input: estimated source lines of code (SLOC)
- Produces: estimated person-months of effort
Key parameters include scale factors (novelty, development flexibility, architecture risk, team cohesion, process maturity) and effort multipliers (reliability requirements, database size, product complexity, execution time constraints, etc.).
Function Points
Function points measure software size based on functionality delivered to the user, independent of programming language:
- Count inputs, outputs, queries, files, and external interfaces
- Apply complexity weights
- Adjust for environmental factors
Function points are language-independent and can be estimated from requirements before any code is written.
13.4 Non-Algorithmic Cost Estimation Techniques
Five alternative approaches:
1. Expert Judgement
The project is estimated by one or more experts based on their experience.
- Advantages: Captures tacit knowledge; can consider unusual project factors
- Disadvantages: Biased by personal experience; may be overconfident; not reproducible
2. Estimation by Analogy
The project is compared to a similar completed project in the same domain.
- Advantages: Accurate if good comparable project data exists
- Disadvantages: Impossible if no comparable project exists; domain knowledge required
3. Parkinson’s Law
“Work expands to fill the time available” — the project costs whatever resources are available.
- Advantages: No overspending (you never exceed the available budget)
- Disadvantages: The system is usually unfinished — you deliver whatever fits in the available time/budget
4. Pricing to Win
The project is priced at whatever the customer is willing to pay.
- Advantages: You win the contract
- Disadvantages: The probability that the customer gets the system they actually want is small; costs often do not reflect the work required; leads to scope cuts or overruns
5. Wideband Delphi
A structured expert elicitation technique:
- Each expert independently estimates
- Estimates are shared (anonymously or openly)
- Experts discuss, then re-estimate
- Repeat until convergence
Reduces individual bias; takes advantage of collective wisdom.
13.5 Top-Down vs. Bottom-Up Estimation
All of the above techniques can be applied in either direction:
| Approach | How It Works | Strengths | Weaknesses |
|---|---|---|---|
| Top-down | Start at system level; estimate overall functionality and delivery through subsystems | Usable with little knowledge; factors in integration, configuration, and documentation costs | Can underestimate low-level implementation problems |
| Bottom-up | Start at component level; estimate each piece; aggregate for system total | Accurate for components whose architecture is known; captures low-level details | May underestimate system-level activities such as integration; requires architecture to be known |
13.6 COCOMO II in More Detail
COCOMO II models three stages of a project’s development:
- Application Composition — early prototyping; measured in “object points”
- Early Design — rough design; measured in function points
- Post-Architecture — detailed design; measured in SLOC
The basic Post-Architecture formula:
PM = A × Size^SF × EM₁ × EM₂ × ... × EMₙ
Where:
PM= person-months of effortSF= sum of scale factor exponents (adjusts for project characteristics)EM= effort multipliers (cost drivers)
Scale factors capture economies/diseconomies of scale: larger projects are disproportionately harder to manage.
Notes compiled from CS 446 / ECE 452 / CS 646 course materials, Winter 2021. Instructor: Shane McIntosh (material adapted from Mei Nagappan, Zhen Ming Jiang, Ahmed E. Hassan, and Reid Holmes). Sources include Tailor et al., Garlan & Shaw, GoF Design Patterns, Hughes & Cotterell, Sommerville, and Russell & Taylor.