Software EngineeringUnit 420 min read
Design Principles & Patterns: UML, SOLID, GoF, MVC, Layers & Reuse
Unit 4 of Software Engineering covers fundamental design principles (SOLID, DRY, KISS), architectural patterns (MVC, Layered, Pipe-Filter), GoF design patterns (Singleton, Observer, Factory), UML modeling, and software reuse strategies—with real-world examples from eSewa, Daraz, and banking systems.
Core Concepts: Why Design Matters
1. Principles vs. Patterns
- Principles are guidelines (e.g., "Single Responsibility Principle") that shape how you write code.
- Patterns are proven solutions to recurring problems (e.g., "Singleton pattern for one global instance").
mindmap
root((Software Design))
Principles["Principles (Rules)"]
SOLID["5 Rules for Clean Code"]
DRY["Don't Repeat Yourself"]
KISS["Keep It Simple, Stupid"]
Patterns["Patterns (Templates)"]
GoF["23 Gang-of-Four Patterns"]
Architectural["MVC, Layered, Microservices"]
Anti["Anti-patterns to Avoid"]2. SOLID Principles: The 5 Pillars of Clean Code
SOLID is an acronym for five principles that make software maintainable and scalable. Violating these leads to "spaghetti code" (tightly coupled, hard-to-change systems).
A. Single Responsibility Principle (SRP)
"A class should have only one reason to change."
Example (Violation → Fix):
// ❌ Violates SRP: User class handles *both* data *and* validation
class User {
String name;
void saveToDB() { ... }
boolean isValidEmail(String email) { ... } // Should be in EmailValidator!
}
// ✅ Fixed: Separate classes for each responsibility
class User { String name; void saveToDB(); }
class EmailValidator { boolean isValid(String email); }
Real-World Tie-In:
- eSewa’s Payment System:
- ❌ Bad: A single
PaymentServiceclass handles user authentication, transaction logging, and fraud detection. - ✅ Good: Split into
AuthService,TransactionLogger,FraudDetector(each with one job). - Why? If fraud rules change, only
FraudDetectorneeds updates—no risk of breaking authentication.
- ❌ Bad: A single
B. Open/Closed Principle (OCP)
"Software entities should be open for extension but closed for modification."
Example (Violation → Fix):
// ❌ Closed for extension: Adding a new shape requires modifying AreaCalculator
class AreaCalculator {
double calculate(Shape shape) {
if (shape instanceof Circle) return 3.14 * shape.radius * shape.radius;
else if (shape instanceof Square) return shape.side * shape.side;
// ❌ What if we add a Triangle later? We must edit this method!
}
}
// ✅ Open for extension: New shapes add *new classes*, not edits
interface Shape { double area(); }
class Circle implements Shape { public double area() { ... } }
class Square implements Shape { public double area() { ... } }
class Triangle implements Shape { public double area() { ... } } // ✅ No changes to AreaCalculator!
Real-World Tie-In:
- Daraz’s Order Processing:
- ❌ Bad: A single
OrderProcessorclass hasif-elseblocks forCashOnDelivery,Khalti,Esewa. Adding a new payment method requires editing the class. - ✅ Good: Each payment method is a separate class implementing
PaymentStrategyinterface. New methods (e.g.,Fonepay) are added without touchingOrderProcessor. - Why? Daraz can add payment options without breaking existing code (critical for Nepal’s dynamic fintech scene).
- ❌ Bad: A single
C. Liskov Substitution Principle (LSP)
"Subtypes must be substitutable for their base types without breaking the program."
Example (Violation):
class Rectangle {
int width, height;
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
}
class Square extends Rectangle {
void setWidth(int w) { width = height = w; } // ❌ Breaks LSP!
void setHeight(int h) { height = width = h; }
}
Problem: If you substitute a Square for a Rectangle, calling setWidth(5) and setHeight(10) won’t work as expected (square forces equal sides).
Real-World Tie-In:
- NTC’s Traffic Light System:
- ❌ Bad: A
TrafficLightbase class withchangeColor()is subclassed intoUrbanLightandHighwayLight. IfHighwayLightoverrideschangeColor()to ignore pedestrian signals, it violates LSP. - ✅ Good: Separate
UrbanTrafficLightandHighwayTrafficLightwith distinct interfaces. No substitution confusion.
- ❌ Bad: A
D. Interface Segregation Principle (ISP)
"Clients should not be forced to depend on interfaces they don’t use."
Example (Violation → Fix):
// ❌ Fat interface: Worker forced to implement unused methods
interface Worker {
void work();
void eat(); // ❌ Robot doesn’t eat!
}
class HumanWorker implements Worker { ... }
class RobotWorker implements Worker { // ❌ Must implement eat() even though it can't!
public void eat() { throw new UnsupportedOperationException(); }
}
// ✅ Segregated interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
class HumanWorker implements Workable, Eatable { ... }
class RobotWorker implements Workable { ... } // ✅ No forced eat()
Real-World Tie-In:
- Pathao’s Driver App:
- ❌ Bad: A single
Driverinterface forces all drivers to implementdeliverFood(),deliverGroceries(), anddeliverMedicine(). - ✅ Good: Split into
FoodDeliveryDriver,GroceryDeliveryDriver, etc., each implementing only relevant methods. - Why? Saves battery and reduces app complexity for drivers who don’t offer all services.
- ❌ Bad: A single
E. Dependency Inversion Principle (DIP)
"High-level modules should not depend on low-level modules. Both should depend on abstractions."
Example (Violation → Fix):
// ❌ High-level class depends on low-level class
class LightBulb { void turnOn() { ... } }
class Switch {
LightBulb bulb;
void operate() { bulb.turnOn(); } // ❌ Tight coupling!
}
// ✅ Depend on abstraction (interface)
interface Switchable { void turnOn(); }
class LightBulb implements Switchable { ... }
class Switch {
Switchable device; // ✅ Can now work with any Switchable (e.g., Fan, AC)
void operate() { device.turnOn(); }
}
Real-World Tie-In:
- Nepal Rastra Bank’s Loan System:
- ❌ Bad:
LoanProcessordirectly callsBankDatabase.save(). - ✅ Good:
LoanProcessordepends onDataStorageinterface. In testing, you can mockDataStoragewithout touchingLoanProcessor. - Why? Enables unit testing and swapping databases (e.g., from SQL to NoSQL) without rewriting business logic.
- ❌ Bad:
3. Design Patterns: GoF’s 23 Solutions
The Gang of Four (GoF) patterns are categorized into three groups:
A. Singleton Pattern
Problem: Ensure a class has only one instance and provide a global point of access. Use Case: Configuration managers, logging systems, database connections.
Example:
class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() {} // Private constructor
public static DatabaseConnection getInstance() {
if (instance == null) instance = new DatabaseConnection();
return instance;
}
}
Real-World Tie-In:
- eSewa’s Transaction Log:
- Only one
TransactionLoggerinstance writes to the database to avoid duplicate logs. - Why? Prevents race conditions and ensures all transactions are logged consistently.
- Only one
Thread-Safety Warning:
// ❌ Not thread-safe (race condition)
public static DatabaseConnection getInstance() {
if (instance == null) instance = new DatabaseConnection(); // ❌ Two threads can pass this check!
return instance;
}
// ✅ Thread-safe (double-checked locking)
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) instance = new DatabaseConnection();
}
}
return instance;
}
B. Observer Pattern
Problem: Notify multiple objects about changes in another object (e.g., events, state changes). Use Case: UI updates, publish-subscribe systems, stock market tickers.
Example:
interface Observer { void update(String message); }
class NewsAgency {
List<Observer> observers = new ArrayList<>();
void addObserver(Observer o) { observers.add(o); }
void notifyObservers(String news) {
for (Observer o : observers) o.update(news);
}
}
class NcellUser implements Observer {
public void update(String news) { System.out.println("Ncell Alert: " + news); }
}
Real-World Tie-In:
- WhatsApp Notifications:
- Your phone (
Observer) subscribes to WhatsApp’sNewsAgency. - When a new message arrives, WhatsApp notifies all subscribed devices (your phone + tablet).
- Why? Decouples the messaging system from devices—new devices can subscribe without changing WhatsApp’s core.
- Your phone (
C. Factory Pattern
Problem: Defer instantiation to subclasses (avoid if-else for object creation).
Use Case: Payment method selection, GUI component creation.
Example:
interface PaymentMethod { void process(); }
class KhaltiPayment implements PaymentMethod { ... }
class EsewaPayment implements PaymentMethod { ... }
class PaymentFactory {
public PaymentMethod createPayment(String type) {
if (type.equals("khalti")) return new KhaltiPayment();
else if (type.equals("esewa")) return new EsewaPayment();
else throw new IllegalArgumentException();
}
}
Real-World Tie-In:
- Daraz’s Checkout Page:
- Instead of hardcoding
if (payment == "khalti") { ... }, Daraz uses aPaymentFactoryto create the correct payment object. - Why? Adding a new payment method (e.g.,
Fonepay) only requires a new class—not editing the checkout logic.
- Instead of hardcoding
4. Architectural Patterns
A. Model-View-Controller (MVC)
Structure:
classDiagram
class Model {
+data: Object
+businessLogic()
}
class View {
+display(model: Object)
}
class Controller {
+handleInput(event: Event)
+updateModel(data: Object)
}
Model "1" --> "1" View : updates
View "1" --> "1" Controller : user actions
Controller "1" --> "1" Model : changes
note for Model "Core data + rules"
note for View "UI rendering"
note for Controller "Input processing"
caption MVC Flow with Nepali Example: Daraz’s Product PageReal-World Tie-In:
- eSewa’s Web App:
- Model: User account data, transaction records.
- View: HTML pages showing balances, transaction history.
- Controller: Handles "Pay Bill" button clicks, validates inputs, updates the Model.
- Why? Separates concerns—UI changes (View) don’t affect business logic (Model).
Example Workflow:
- User clicks "Pay Electricity Bill" (View → Controller).
- Controller validates input (e.g., correct meter number) and calls
TransactionService(Model). - Model processes payment and updates the database.
- Controller tells View to show "Payment Successful" message.
B. Layered Architecture
Structure:
Real-World Tie-In:
- Nepal Stock Exchange (NEPSE) Trading System:
- Presentation Layer: Web/mobile app showing stock prices.
- Application Layer: Order matching logic (e.g., "Buy 100 shares of NMB at ₹100").
- Domain Layer: Rules like "No short selling."
- Data Layer: SQL database storing trades.
- Why? If NEPSE adds a new trading rule (e.g., "Tax on high-frequency trades"), only the Domain Layer changes.
C. Pipe-Filter Architecture
Structure:
Real-World Tie-In:
- NTC’s Traffic Camera Processing:
- Input: Raw video from cameras.
- Filters:
FrameExtractor: Splits video into frames.ObjectDetector: Identifies vehicles/pedestrians.ViolationChecker: Flags jaywalking/speeding.
- Output: Alerts sent to traffic police.
- Why? Each filter is independent—upgrading the
ObjectDetector(e.g., to use AI) doesn’t break others.
5. Software Reuse
Definition: Using existing software components to build new systems faster and with fewer bugs.
A. Types of Reuse
| Type | Description | Example |
|---|---|---|
| Code Reuse | Copy-paste or inherit existing code | Using a Logger class in multiple projects |
| Component Reuse | Plug-in modules (e.g., libraries) | React.js for UI, Spring for backend |
| Architecture Reuse | Reusing high-level designs | MVC for web apps, Microservices for scalable systems |
| Domain Reuse | Industry-specific templates | Hospital management software templates |
Real-World Tie-In:
- Khalti’s SDK for Apps:
- Developers reuse Khalti’s pre-built
PaymentComponentinstead of writing payment logic from scratch. - Why? Saves time and ensures PCI compliance (security standards for payments).
- Developers reuse Khalti’s pre-built
B. Benefits of Reuse
- Faster Development: No need to reinvent the wheel.
- Lower Costs: Fewer bugs in reused, tested components.
- Consistency: Uniform UI/UX across apps (e.g., all banks use similar login flows).
Example:
6. Anti-Patterns: What to Avoid
Definition: Common solutions that seem to work but cause long-term problems.
| Anti-Pattern | Description | Fix |
|---|---|---|
| God Object | One class does everything | Split into smaller classes (SOLID) |
| Spaghetti Code | Tightly coupled, hard to modify | Use design patterns (e.g., Dependency Injection) |
| Golden Hammer | Overusing one tool/pattern | Choose the right tool for the job |
| Big Ball of Mud | No clear architecture | Adopt MVC/Layered architecture |
Real-World Tie-In:
- Early Ncell Billing System:
- ❌ Anti-Pattern: A single
BillingProcessorclass handled customer data, billing calculations, UI rendering, and database updates. - ✅ Fix: Split into
CustomerService,BillingCalculator,UIRenderer, andDatabaseRepository. - Why? Now Ncell can update billing rules without breaking the UI.
- ❌ Anti-Pattern: A single
In the Real World
eSewa’s Payment System
- Idea Used: Strategy Pattern for payment methods.
- How? Each payment gateway (
Khalti,Esewa,Fonepay) implementsPaymentStrategyinterface. The checkout system delegates payment processing to the selected strategy. - Impact: Adding a new payment method (e.g.,
NMB Bank) requires only a new class—not changing the checkout logic.
Daraz’s Order Processing
- Idea Used: Observer Pattern for notifications.
- How? When an order status changes (e.g., "Shipped"), Daraz notifies the customer (email/SMS), the warehouse (pick item), and the delivery partner (assign driver).
- Impact: Decouples order processing from notification systems—easy to add new notification channels (e.g., WhatsApp alerts).
Nepal Rastra Bank’s Loan Approval
- Idea Used: Layered Architecture.
- How? The loan system has:
- Presentation Layer: Web/mobile app for customers.
- Application Layer: Business rules (e.g., "Income > 3x EMI").
- Domain Layer: Loan types (home, car, personal).
- Data Layer: SQL database.
- Impact: If NRB changes loan eligibility rules, only the Domain Layer is updated—no UI or database changes.
Exam Tip
What Examiners Love to See
Diagrams > Text
- Always draw class diagrams for patterns (e.g., MVC, Observer) and sequence diagrams for interactions (e.g., payment processing).
- Example: For the Singleton pattern, show:
- A class with a
private static instance. - A
getInstance()method with lazy initialization.
- A class with a
Real-World Mapping
- Link abstract concepts to Nepali tech companies (e.g., "eSewa uses the Strategy Pattern for payments").
- Avoid generic examples like "ATM machines"—use specific systems you’ve heard of.
Code Snippets with Explanations
- Show before/after code for SOLID violations (e.g., SRP, OCP).
- Highlight key lines (e.g.,
interface,abstract class,dependency injection).
Trade-offs
- Discuss pros/cons of patterns:
- Singleton: Simple but can cause global state issues.
- Observer: Decouples objects but can lead to memory leaks if not managed.
- Discuss pros/cons of patterns:
Common Pitfalls
- Thread safety in Singleton (always mention
synchronizedordouble-checked locking). - Over-engineering (e.g., using a Factory when a simple
if-elsesuffices).
- Thread safety in Singleton (always mention
Past Exam Patterns
- Calculation Questions: Expect COCOMO (Unit 2) or effort estimation using KLOC (Thousands of Lines of Code). For Unit 4, focus on design trade-offs (e.g., "Why would you choose MVC over Layered?").
- Differentiation: Always compare two concepts (e.g., "Verify vs. Validate," "Prototyping vs. Agile").
- Diagram-Based: Sketch UML class diagrams for patterns (e.g., Observer, Factory) or architecture diagrams (MVC, Layered).
Model Answer Structure
For a 5-mark question like "Explain the Observer Pattern with a real-world example":
- Definition (1 mark): "Observer is a behavioral pattern where an object (Subject) maintains a list of dependents (Observers) and notifies them of state changes."
- Components (1 mark):
- Subject: Maintains state and notifies observers (e.g.,
NewsAgency). - Observer: Gets updates (e.g.,
NcellUser).
- Subject: Maintains state and notifies observers (e.g.,
- Diagram (1 mark): Draw a UML class diagram or sequence diagram.
- Example (2 marks):
- WhatsApp: Subject = WhatsApp server; Observers = your phone, tablet, laptop.
- Code Snippet: Show
addObserver(),notifyObservers(), andupdate()methods.
Summary Table: Key Concepts
| Concept | Key Idea | When to Use | Example |
|---|---|---|---|
| SRP | One class, one responsibility | Any class doing multiple jobs | Split User into User + EmailValidator |
| OCP | Extend without modifying | Adding new features (e.g., payment methods) | PaymentStrategy interface |
| Singleton | Single instance | Logging, configuration, DB connection | DatabaseConnection |
| Observer | Event-driven updates | Notifications, UI updates | WhatsApp messages, stock tickers |
| MVC | Separate Model, View, Controller | Web/mobile apps | eSewa’s payment page |
| Layered | Stacked responsibilities | Enterprise systems | NEPSE trading platform |
| Factory | Defer object creation | Dynamic object creation | Daraz’s payment method selection |
Based on the TU BSc CSIT syllabus for Software Engineering (CSC364), unit 4.
Discussion
Loading…