Ready to get started?

Check out the plugin on GitHub and start using it today.

S O L I D DRY KISS YAGNI

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

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