Software Architecture
Master design patterns, SOLID principles, and architectural best practices. Build maintainable, scalable, and robust WordPress applications.
SOLID Principles
Essentials
GoF Patterns
Architecture Patterns
Best Practices
Featured Quote
“Make it work, make it right, make it fast”
The golden rule of software development
SOLID Principles
Five fundamental principles for object-oriented design
S
Single Responsibility
One class – one responsibility
Show details
O
Open/Closed
Open for extension, closed for modification
Show details
L
Liskov Substitution
Subtypes must be substitutable
Show details
I
Interface Segregation
Many specific interfaces better than one
Show details
D
Dependency Inversion
Depend on abstractions, not concretions
Show details
Single Responsibility
Single Responsibility
A class should have one, and only one, reason to change. Each class should focus on a single task or responsibility.
Benefits
- Easier to test
- Easier to maintain
- Easier to understand
- Reduces coupling
Code Example
// Bad โ
class User {
saveToDatabase() { }
sendEmail() { }
generateReport() { }
}
// Good โ
class User {
constructor(data) { }
}
class UserRepository {
save(user) { }
}
class EmailService {
send(user, message) { }
}
class ReportGenerator {
generate(user) { }
}
Open/Closed
Open/Closed
Software entities should be open for extension but closed for modification. Add new functionality without changing existing code.
Benefits
- Flexible code
- Easy to extend
- Minimal risk when adding features
- Promotes reusability
Code Example
// Bad โ
class AreaCalculator {
calculate(shape) {
if (shape.type === 'circle') {
return Math.PI * shape.radius ** 2;
}
if (shape.type === 'rectangle') {
return shape.width * shape.height;
}
}
}
// Good โ
interface Shape {
calculateArea(): number;
}
class Circle implements Shape {
calculateArea() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle implements Shape {
calculateArea() {
return this.width * this.height;
}
}
Liskov Substitution
Liskov Substitution
Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
Benefits
- Predictable behavior
- Correct inheritance
- Type safety
- Reliable polymorphism
Code Example
// Bad โ
class Bird {
fly() { }
}
class Penguin extends Bird {
fly() {
throw new Error("Can't fly!");
}
}
// Good โ
class Bird {
move() { }
}
class FlyingBird extends Bird {
fly() { }
}
class Penguin extends Bird {
swim() { }
}
Interface Segregation
Interface Segregation
Clients should not be forced to depend on interfaces they do not use. Split large interfaces into smaller, specific ones.
Benefits
- Flexible interfaces
- No unnecessary methods
- Better code organization
- Easier to implement
Code Example
// Bad โ
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
class Robot implements Worker {
work() { }
eat() { /* Robot doesn't eat! */ }
sleep() { /* Robot doesn't sleep! */ }
}
// Good โ
interface Workable { work(): void; }
interface Eatable { eat(): void; }
interface Sleepable { sleep(): void; }
class Robot implements Workable {
work() { }
}
Dependency Inversion
Dependency Inversion
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Benefits
- Flexible architecture
- Easy to swap implementations
- Better testability
- Reduced coupling
Code Example
// Bad โ
class MySQLDatabase {
save(data) { }
}
class UserService {
constructor() {
this.db = new MySQLDatabase();
}
}
// Good โ
interface Database { save(data): void; }
class MySQLDatabase implements Database {
save(data) { }
}
class UserService {
constructor(private db: Database) { }
}
Essential Principles
Fundamental rules that every developer should follow
DRY
Don’t Repeat Yourself
Every piece of knowledge must have a single, unambiguous representation in the system.
// Good โ
function getUserFullName() {
return user.firstName + ' ' + user.lastName;
}
KISS
Keep It Simple, Stupid
Most systems work best if they are kept simple rather than made complex.
// Good โ
const result = arr
.filter(val => val > 5)
.map(val => val * 2);
YAGNI
You Aren’t Gonna Need It
Don’t add functionality until it’s necessary. Avoid over-engineering.
// Good โ
class User {
login() {}
logout() {}
}
Gang of Four Patterns
23 classic design patterns for object-oriented software
All Patterns
Creational
Structural
Behavioral
Creational
Object creation mechanisms
Singleton
Ensure a class has only one instance
Database connections, configuration managers
class Database {
static instance;
static getInstance() {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
}
Factory
Create objects without specifying exact class
UI components, document parsers
class ButtonFactory {
createButton(os) {
if (os === 'Windows') return new WindowsButton();
if (os === 'Mac') return new MacButton();
}
}
Builder
Construct complex objects step by step
Complex object creation, query builders
class QueryBuilder {
select(fields) { this.query += fields; return this; }
build() { return this.query; }
}
Prototype
Clone objects instead of creating new
Object copying, game entities
class Sheep {
clone() { return new Sheep(this.name); }
}
Structural
How objects are composed
Adapter
Make incompatible interfaces work together
Third-party library integration
class PaymentAdapter {
constructor(oldPayment) { this.old = oldPayment; }
pay(amount) { this.old.oldPay(amount); }
}
Decorator
Add behavior to objects dynamically
Extend functionality without modifying code
class MilkDecorator {
constructor(coffee) { this.coffee = coffee; }
cost() { return this.coffee.cost() + 2; }
}
Facade
Simplified interface to complex subsystem
Hide complexity, provide simple API
class VideoConverter {
convert(file, format) {
const codec = new CodecFactory().get(format);
}
}
Proxy
Placeholder for another object
Lazy loading, access control, logging
class ProxyImage {
display() {
if (!this.real) this.real = new RealImage();
}
}
Behavioral
Communication between objects
Observer
Subscribe to events and get notifications
Event handling, pub/sub systems
class Subject {
notify(data) { this.observers.forEach(o => o.update(data)); }
}
Strategy
Select algorithm at runtime
Payment methods, sorting algorithms
class ShoppingCart {
constructor(strategy) { this.strategy = strategy; }
}
Command
Encapsulate request as an object
Undo/redo, macro recording, queuing
class CommandManager {
execute(cmd) { cmd.execute(); this.history.push(cmd); }
}
Iterator
Traverse collection without exposing structure
Custom collections, tree traversal
class BookIterator {
hasNext() { return this.index < this.books.length; }
}
Real-World Practice
How these patterns work in actual WordPress development
WP
Hooks System
Observer Pattern
add_action() and add_filter() – classic Observer implementation
add_action('save_post', function($post_id) {
notify_users($post_id);
update_cache($post_id);
});
WP
Woo Payment Gateways
Strategy Pattern
Different payment strategies selected at runtime
class WC_Gateway_Stripe extends WC_Payment_Gateway {
public function process_payment($order_id) {}
}
WP
WP_Query
Facade Pattern
Simple API that hides complex database operations
$query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 10
]);
WP
Gutenberg Blocks
Factory Pattern
registerBlockType() creates different block types
registerBlockType('my-plugin/custom-block', {
title: 'Custom Block'
});
WP
Plugin Architecture
Dependency Inversion
Plugins depend on API abstractions, not concrete implementations
add_filter('the_content', 'my_filter');
// Depends on abstraction (hooks)
WP
Widget System
Template Method
WP_Widget defines structure, subclasses implement details
class My_Widget extends WP_Widget {
public function widget($args, $instance) {}
}
Architectural Patterns
High-level patterns for structuring entire applications
MVC
Model-View-Controller
Separates application into three interconnected components
Components:
Model: Data & Logic
View: UI Presentation
Controller: Input
Benefits:
- Clear separation
- Parallel development
- Multiple views
- Easy to modify
View <--> Controller --> Model
MVVM
Model-View-ViewModel
Separates UI from business logic with data binding
Components:
Model: Data
View: UI
ViewModel: Logic
Benefits:
- Data binding
- Testability
- Separation of concerns
- Reusable ViewModels
View <--> ViewModel <--> Model
Layered
Layered Architecture
Organizes code into horizontal layers
Components:
- Presentation Layer
- Business Layer
- Data Access Layer
- Database
Benefits:
- Easy to understand
- Maintainable
- Testable
- Reusable layers
Presentation > Business > Data Access > DB
Hexagonal
Ports & Adapters
Application core independent of external concerns
Components:
- Core Domain
- Ports (Interfaces)
- Adapters
- External Systems
Benefits:
- Testability
- Flexibility
- Independence
- Swappable dependencies
External > Adapter > Port > Core
Architecture Best Practices
Apply these principles consistently to build robust, maintainable, and scalable applications
Modularity
Break down complex systems into smaller, manageable modules
Encapsulation
Hide internal details and expose only necessary interfaces
Abstraction
Work with high-level concepts rather than low-level details
Loose Coupling
Minimize dependencies between components for flexibility