What Is Architecture?&Independence
- **Chapter 15: What Is Architecture?**
- **Key Concepts**:
- **Code Example: Layered Architecture**:
- **Chapter 16: Independence**
- **Key Concepts**:
- **Code Example: Dependency Inversion & Interfaces**:
- **Combined Example: Boundaries & Independence**
- **Key Takeaways**:
- **Chapters 15 & 16 Overview**
- **10 Hard Difficulty Multiple-Choice Questions**
- **Test Code Examples**
Chapter 15: What Is Architecture?
Key Concepts:
- Definition: Architecture is the shape of a system defined by components, dependencies, and boundaries that manage complexity and enable evolution.
- Core Goals:
- Manage Complexity: Decouple components to isolate changes.
- Keep Options Open: Delay decisions (e.g., frameworks, databases) to avoid premature constraints.
- Support Use Cases: Ensure the system delivers business value.
- Layers & Boundaries:
- Separate high-level policy (business logic) from low-level details (I/O, UI).
- Use boundaries (interfaces, abstractions) to isolate volatile components.
Code Example: Layered Architecture:
#include <iostream>
#include <string>
#include <vector>// High-level Policy (Business Logic)
class Order {
public:virtual double calculateTotal() const = 0;virtual ~Order() = default;
};// Low-level Detail (Implementation)
class ConcreteOrder : public Order {
private:std::vector<double> items;
public:void addItem(double price) { items.push_back(price); }double calculateTotal() const override {double total = 0;for (auto price : items) total += price;return total;}
};// Client Code (Depends on Abstraction)
void printTotal(const Order& order) {std::cout << "Total: $" << order.calculateTotal() << std::endl;
}int main() {ConcreteOrder order;order.addItem(10.5);order.addItem(20.3);printTotal(order); // Output: Total: $30.8return 0;
}
Explanation:
- Abstraction:
Order
is an interface defining business rules. - Implementation:
ConcreteOrder
provides the calculation logic. - Dependency Inversion:
printTotal
depends on the abstractOrder
, not concrete details.
Chapter 16: Independence
Key Concepts:
- Component Independence:
- Deployability: Components can be deployed separately (e.g., microservices).
- Developability: Teams work independently on components.
- Replaceability: Swap implementations without breaking the system.
- Decoupling Techniques:
- Dependency Inversion: Depend on abstractions, not concretions.
- Interface Segregation: Split interfaces to avoid unnecessary dependencies.
- Boundary Patterns: Use layers, ports/adapters, or hexagonal architecture.
Code Example: Dependency Inversion & Interfaces:
#include <iostream>
#include <memory>// Abstraction (Port)
class Database {
public:virtual void save(const std::string& data) = 0;virtual ~Database() = default;
};// Low-level Detail (Adapter)
class SQLDatabase : public Database {
public:void save(const std::string& data) override {std::cout << "Saving to SQL: " << data << std::endl;}
};// High-level Policy
class UserService {
private:std::unique_ptr<Database> db;
public:UserService(std::unique_ptr<Database> db) : db(std::move(db)) {}void createUser(const std::string& name) {db->save("User: " + name);}
};int main() {auto sqlDb = std::make_unique<SQLDatabase>();UserService service(std::move(sqlDb));service.createUser("Alice"); // Output: Saving to SQL: User: Alicereturn 0;
}
Explanation:
- Dependency Inversion:
UserService
depends on theDatabase
abstraction. - Testability: Easily swap
SQLDatabase
with aMockDatabase
for testing. - Decoupling: Changes to the database implementation don’t affect
UserService
.
Combined Example: Boundaries & Independence
#include <iostream>
#include <memory>// Port (Interface)
class PaymentGateway {
public:virtual void pay(double amount) = 0;virtual ~PaymentGateway() = default;
};// Adapter 1: PayPal Implementation
class PayPalGateway : public PaymentGateway {
public:void pay(double amount) override {std::cout << "Paying $" << amount << " via PayPal." << std::endl;}
};// Adapter 2: Stripe Implementation
class StripeGateway : public PaymentGateway {
public:void pay(double amount) override {std::cout << "Paying $" << amount << " via Stripe." << std::endl;}
};// High-level Policy
class OrderService {
private:std::unique_ptr<PaymentGateway> gateway;
public:OrderService(std::unique_ptr<PaymentGateway> gateway) : gateway(std::move(gateway)) {}void processOrder(double amount) {gateway->pay(amount);}
};int main() {// Swap payment gateways without changing OrderService.auto paypal = std::make_unique<PayPalGateway>();OrderService service1(std::move(paypal));service1.processOrder(100.0); // Output: Paying $100 via PayPal.auto stripe = std::make_unique<StripeGateway>();OrderService service2(std::move(stripe));service2.processOrder(200.0); // Output: Paying $200 via Stripe.return 0;
}
Explanation:
- Boundary:
PaymentGateway
defines a port for payment processing. - Independence:
OrderService
is decoupled from specific payment providers. - Flexibility: New gateways (e.g.,
BitcoinGateway
) can be added without modifying core logic.
Key Takeaways:
- Architecture = Managed Dependencies: Use abstractions to isolate volatility.
- Delay Decisions: Keep infrastructure (databases, UIs) replaceable.
- Test with Mocks: Compile-time polymorphism enables testing without concrete dependencies.
Chapters 15 & 16 Overview
Chapter 15: “What Is Architecture?”
- Focuses on defining architecture as the structure of components, their relationships, and design principles guiding evolution.
- Key topics:
- Architectural goals: Managing dependencies between components, enabling flexibility, and delaying decisions.
- Impact on development phases: Deployment, operation, maintenance, and evolution.
- Device independence and avoiding premature commitment to frameworks/databases.
Chapter 16: “Independence”
- Discusses designing systems to achieve independent components for flexibility and scalability.
- Key topics:
- Horizontal vs. vertical decoupling: Separating use cases, layers (UI, business logic, data).
- Delaying decisions: Keeping options open by abstracting volatile components (e.g., databases).
- Avoiding duplication while balancing decoupling.
10 Hard Difficulty Multiple-Choice Questions
Question 1
Which are core goals of software architecture according to Chapter 15?
A) Maximize code performance.
B) Delay irreversible decisions.
C) Enforce strict coding standards.
D) Manage dependencies between components.
Question 2
What does “device independence” imply in Clean Architecture?
A) Code must run on all hardware without modification.
B) Business logic should not depend on specific I/O devices or frameworks.
C) Use cross-platform libraries for all components.
D) Avoid using third-party APIs.
Question 3
Which principle helps achieve independent deployability of components?
A) Stable Dependencies Principle.
B) Interface Segregation Principle.
C) Single Responsibility Principle.
D) Common Closure Principle.
Question 4
Why is horizontal layering insufficient for true independence?
A) It enforces rigid dependencies between layers.
B) It doesn’t address vertical use-case boundaries.
C) It increases deployment complexity.
D) It violates the Open-Closed Principle.
Question 5
How does Clean Architecture handle database dependencies?
A) Business logic directly depends on SQL queries.
B) Database access is abstracted via interfaces.
C) Use a single global database connection.
D) Business logic and database are tightly coupled.
Question 6
Which is a valid strategy to delay decisions?
A) Hardcoding configuration values.
B) Using dependency injection for volatile components.
C) Relying on concrete framework APIs.
D) Embedding business rules in UI code.
Question 7
What is the risk of violating the Stable Dependencies Principle?
A) High-level policies depend on low-level details.
B) Components cannot be tested independently.
C) Changes propagate unpredictably across the system.
D) Code duplication increases.
Question 8
Which code snippet aligns with Clean Architecture principles?
Snippet 1:
class PaymentProcessor:def __init__(self, db_conn):self.db = db_conndef process(self, amount):self.db.execute("INSERT INTO payments ...")
Snippet 2:
class PaymentGateway(ABC):@abstractmethoddef process_payment(self, amount): passclass SqlPaymentGateway(PaymentGateway):def __init__(self, db_conn): ...def process_payment(self, amount): ...
A) Only Snippet 1.
B) Only Snippet 2.
C) Both.
D) Neither.
Question 9
What problem arises when business logic depends on UI frameworks?
A) UI changes force business logic rewrites.
B) Business logic becomes reusable across UIs.
C) It simplifies testing.
D) It improves deployment speed.
Question 10
Which is an example of vertical decoupling?
A) Separating code into MVC layers.
B) Isolating payment processing from user management.
C) Using interfaces for database access.
D) Implementing a plugin architecture.
Answers & Explanations
Answer 1
Correct: B, D
Explanation:
- B) Delaying decisions prevents premature commitments (Ch15).
- D) Managing dependencies is a core architectural goal (Ch15).
- A) Performance is secondary to structural goals.
- C) Coding standards are implementation details, not architectural goals.
Answer 2
Correct: B
Explanation:
- B) Device independence means business logic isn’t tied to specific I/O (Ch15).
- A) Code may require device-specific drivers but abstracts them.
- C/D) Irrelevant to the core concept.
Answer 3
Correct: A
Explanation:
- A) Stable Dependencies Principle ensures components depend only on stable abstractions (Ch16).
- B/C/D) Address cohesion or interface design, not deployability.
Answer 4
Correct: B
Explanation:
- B) Horizontal layers (e.g., UI, business logic) don’t isolate use cases vertically (Ch16).
- A) Rigid dependencies are a symptom, not the root cause.
Answer 5
Correct: B
Explanation:
- B) Abstracting database access via interfaces decouples business logic (Ch15).
- A/C/D) Create tight coupling and violate independence.
Answer 6
Correct: B
Explanation:
- B) Dependency injection defers concrete implementation choices (Ch16).
- A/C/D) Fix decisions early, reducing flexibility.
Answer 7
Correct: A, C
Explanation:
- A) High-level components depending on low-level details creates fragility.
- C) Violations cause cascading changes (Ch16).
- B/D) Unrelated to dependency stability.
Answer 8
Correct: B
Explanation:
- Snippet 2 uses abstraction (
PaymentGateway
), aligning with DIP (Ch11/15). - Snippet 1 directly depends on a database, violating decoupling.
Answer 9
Correct: A
Explanation:
- A) Tight coupling forces rewrites when UI changes (Ch16).
- B/D) Independence improves reusability and deployment.
Answer 10
Correct: B
Explanation:
- B) Vertical decoupling isolates use cases (e.g., payment vs. user management) (Ch16).
- A/C) Horizontal layering or interface use.
- D) Plugin architecture is a horizontal strategy.
Test Code Examples
Example for Q8 (Snippet 2):
from abc import ABC, abstractmethodclass PaymentGateway(ABC):@abstractmethoddef process_payment(self, amount): passclass SqlPaymentGateway(PaymentGateway):def __init__(self, db_conn):self.db = db_conndef process_payment(self, amount):self.db.execute("INSERT INTO payments ...")# Test
class MockDb:def execute(self, query): print(f"Mock: {query}")gateway = SqlPaymentGateway(MockDb())
gateway.process_payment(100) # Output: "Mock: INSERT INTO payments ..."
Compilation: This Python code runs as-is, demonstrating dependency inversion.
Example for Q5:
class DatabaseInterface(ABC):@abstractmethoddef save_payment(self, amount): passclass PostgresAdapter(DatabaseInterface):def save_payment(self, amount): ...class BusinessLogic:def __init__(self, db: DatabaseInterface):self.db = dbdef process_payment(self, amount):self.db.save_payment(amount)
Test: BusinessLogic
depends on an abstraction, enabling database swaps without code changes.