Complete Guide | Lesson 6: Interfaces → Lesson 7: Coupling & SOLID → Lesson 8: Design Patterns (May 14)
Every connection between two pieces of code is a chain. The question is whether you're building a bridge or a trap.
When Module A calls Module B, which calls Module C, which reads from Database D, which formats results through Template E, you don't have a system. You have a hostage situation. Change anything in D and E breaks. Modify B and A panics. Touch C and the whole thing falls over on a Friday afternoon while you're trying to leave for the weekend.
This is Lesson 7, and it maps to Geburah on the Tree of Life. Geburah means "Severity" or "Discipline." It's the sephira that prunes, restricts, and enforces boundaries. Where Chesed (Lesson 6) expanded possibility through interfaces, Geburah imposes the discipline to use them properly. Good software doesn't just need creativity. It needs rules. Constraints. The willingness to say "no, this module should not know about that one."
Coupling: The Chains Between Modules
Coupling measures how much one module depends on another. The tighter the coupling, the more they're chained together. The looser the coupling, the more independently they operate.
Tight Coupling (The Hostage Situation)
Tightly coupled code looks like this:
1CLASS OrderProcessor
2 METHOD processOrder(order)
3 // Directly creates a specific payment processor
4 paypal = new PayPalProcessor()
5 paypal.charge(order.total)
6
7 // Directly formats a specific email
8 email = "Dear " + order.customer.name + ", your order #" + order.id + " ..."
9 smtpServer = new SmtpServer("mail.company.com", 587)
10 smtpServer.send(order.customer.email, email)
11
12 // Directly writes to a specific database
13 db = new MySqlConnection("localhost:3306/orders")
14 db.execute("INSERT INTO orders VALUES (" + order.id + "...)")
15 END METHOD
16END CLASSOrderProcessor is welded to PayPal, a specific SMTP server, specific email formatting, and MySQL. Want to switch to Stripe? Rewrite processOrder(). Move to a different email provider? Rewrite processOrder(). Switch to PostgreSQL? You guessed it. Every change to any dependency requires modifying this class.
Loose Coupling (The Pen Pals)
Loosely coupled code communicates through interfaces, not implementations:
1CLASS OrderProcessor
2 paymentService: IPaymentService
3 notifier: INotifier
4 repository: IOrderRepository
5
6 CONSTRUCTOR(payment, notifier, repository)
7 this.paymentService = payment
8 this.notifier = notifier
9 this.repository = repository
10 END CONSTRUCTOR
11
12 METHOD processOrder(order)
13 this.paymentService.charge(order.total)
14 this.notifier.notify(order.customer, "Order confirmed")
15 this.repository.save(order)
16 END METHOD
17END CLASSOrderProcessor doesn't know if payment goes through PayPal, Stripe, or carrier pigeon. It doesn't know if notifications go by email, SMS, or push notification. It doesn't know if the database is MySQL, MongoDB, or a filing cabinet. It talks to interfaces. Implementations are injected from outside.
Want to switch to Stripe? Create a new StripePaymentService that implements IPaymentService. Plug it in. Zero changes to OrderProcessor.
The Deployment Test
Here's a simple way to measure coupling: can you deploy one module without deploying another? If changing the notification system requires redeploying the order processor, they're too coupled. If they can be deployed independently, they're properly decoupled.
Cohesion: The Kitchen Drawer Test
Cohesion measures how closely the members of a module are related to each other. High cohesion means everything in a class serves the same purpose. Low cohesion means you have a junk drawer.
High cohesion (everything belongs together):
1CLASS UserAuthentication
2 METHOD login(username, password)
3 METHOD logout(userId)
4 METHOD resetPassword(email)
5 METHOD validateToken(token)
6 METHOD refreshToken(token)
7END CLASSEvery method relates to authentication. The class has one clear purpose. You could describe it in one sentence: "This class handles user authentication."
Low cohesion (the kitchen drawer):
1CLASS Utilities
2 METHOD formatDate(date)
3 METHOD sendEmail(to, subject, body)
4 METHOD compressFile(path)
5 METHOD calculateTax(amount, state)
6 METHOD generateThumbnail(image)
7 METHOD encryptPassword(password)
8END CLASSWhat does this class do? Everything. Nothing. It's a grab bag of unrelated functions crammed into one file because someone didn't know where else to put them. Try describing it in one sentence without using "and."
The One-Sentence Cohesion Test
Describe what your class does in one sentence. If you can't do it without using the word "and," your class has low cohesion and should be split. "This class handles authentication." Good. "This class handles authentication and email and file compression and tax calculation." Split it. Now.
Spaghetti, Lasagna, and Ravioli
Yes, these are real software terms. Yes, the industry named code patterns after Italian food. Here's what they mean:
Spaghetti code: No structure. Everything is tangled with everything else. Logic jumps from function to function with no clear path. Debugging requires tracing a single strand through a plate of tangled noodles.
Lasagna code: Too many layers. An HTTP request passes through 17 abstraction layers before touching the database. Each layer adds overhead and complexity without adding clarity. Over-engineered.
Ravioli code: Small, self-contained, well-bounded modules. Each "raviolo" is a complete unit with a clear purpose. They don't leak into each other. They communicate through well-defined interfaces. This is the goal.
The SOLID Principles
SOLID is an acronym coined by Robert C. Martin ("Uncle Bob") that captures five principles of object-oriented design. They're not rules. They're guidelines. But following them consistently produces code that's dramatically easier to maintain, test, and extend.
S: Single Responsibility Principle
A class should have only one reason to change.
A knife cuts. It doesn't also serve as a hammer, a screwdriver, and a bottle opener. (Swiss Army knives exist, but nobody wants their production code to be a Swiss Army knife.)
1// BAD: Two reasons to change (business rules AND formatting)
2CLASS Invoice
3 METHOD calculateTotal()
4 METHOD applyDiscount()
5 METHOD generatePdf()
6 METHOD sendEmail()
7END CLASS
8
9// GOOD: Separated responsibilities
10CLASS Invoice
11 METHOD calculateTotal()
12 METHOD applyDiscount()
13END CLASS
14
15CLASS InvoicePrinter
16 METHOD generatePdf(invoice)
17END CLASS
18
19CLASS InvoiceNotifier
20 METHOD sendEmail(invoice)
21END CLASSIf the PDF format changes, only InvoicePrinter changes. If the discount logic changes, only Invoice changes. If the email provider changes, only InvoiceNotifier changes. No class has two masters.
O: Open/Closed Principle
Open for extension, closed for modification.
You should be able to add new behavior without changing existing code. This is exactly what interfaces enable (Lesson 6): add a new IPaymentService implementation without touching the checkout code.
L: Liskov Substitution Principle
Subtypes must be usable wherever their parent type is expected, without breaking behavior.
Remember the Rectangle-Square problem from Lesson 5? Square broke Liskov because changing its width also changed its height, violating the contract that Rectangle established. If your subclass surprises the caller, it violates Liskov.
I: Interface Segregation Principle
Many small interfaces beat one fat interface.
Don't force a fish to implement walk():
1// BAD: One fat interface forces irrelevant implementations
2INTERFACE IAnimal
3 METHOD walk()
4 METHOD swim()
5 METHOD fly()
6END INTERFACE
7// A fish must implement walk() and fly()? Absurd.
8
9// GOOD: Small, focused interfaces
10INTERFACE IWalkable
11 METHOD walk()
12END INTERFACE
13
14INTERFACE ISwimmable
15 METHOD swim()
16END INTERFACE
17
18INTERFACE IFlyable
19 METHOD fly()
20END INTERFACE
21// A fish implements ISwimmable. A duck implements all three.D: Dependency Inversion Principle
Depend on abstractions, not concretions.
We covered this in Lesson 6. High-level modules shouldn't depend on low-level details. Both should depend on interfaces. This is the principle that makes loose coupling possible.
SOLID Quick Reference
S - Single Responsibility: One class, one job, one reason to change. O - Open/Closed: Add new behavior by extending, not modifying. L - Liskov Substitution: Subclasses must honor their parent's contract. I - Interface Segregation: Small, focused interfaces over fat ones. D - Dependency Inversion: Depend on abstractions, not implementations.
When to Bend the Rules
SOLID is not a religion. It's a set of guidelines that works brilliantly for production code and can be overkill for prototypes.
Bend the rules when:
- You're writing a quick script that runs once and gets deleted
- You're prototyping and speed matters more than structure
- Following the principle would create more complexity than it prevents
Follow the rules when:
- Other people will read your code
- The code will live longer than a week
- The system will grow beyond its current size
- You're building anything that needs to be maintained
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Martin Fowler wrote that, and it captures the entire point of SOLID: code isn't for computers. It's for the humans who maintain it.