Glossary

SOLID

SOLID provides useful questions for designing changes. It does not say that more classes, interfaces, and layers automatically make software better.

Short definition

Five guidelines, not five rigid laws.

Each letter in SOLID describes a different design problem: too many reasons to change, extension without risky modification, a subtype that breaks its contract, an overly broad interface, and business rules depending on details. The principles provide a shared language for code review and refactoring.

They are most valuable during a specific change, such as adding a carrier, payment method, or application input. When simple code has no plausible future variant or real boundary, KISS and a direct solution may be better than an abstraction created in advance.

The problem it solves

Change without hidden side effects

SOLID does not judge the beauty of a diagram. It helps find places where changing one thing unexpectedly breaks another, or where a class hides too many decisions.

  • an object that calculates a price, stores an order, and sends an email
  • a long chain of if or switch branches that must be modified in several places for every variant
  • a subtype that cannot safely fulfil the expectations of the base type
  • a client forced to implement or know methods it does not need
  • an application rule tightly coupled to an HTTP client, framework, or particular database

Practical example

Starting payment at checkout

Checkout receives a PaymentInitiator implementation as a dependency. Selecting a particular payment method is meaningful variability, so the contract was not created merely to satisfy the name of a principle. Every implementation returns PaymentStart with the same meaning, so checkout need not know the details of the provider API.

If a payment method could not return the expected state and forced checkout into a special branch for an internal detail, the contract would be poorly designed. It is better to revise the meaning of the contract or separate a genuinely different use case than to conceal the difference with inheritance.

PHP

interface PaymentInitiator
{
    public function start(Order $order): PaymentStart;
}

final class Checkout
{
    public function __construct(private PaymentInitiator $payment) {}

    public function beginPayment(Order $order): PaymentStart
    {
        return $this->payment->start($order);
    }
}

Five principles

Each principle answers a different question

The principles overlap only partially. A problem cannot be solved by blindly applying the SOLID acronym.

  1. S — Single Responsibility Does this part of the code have one cohesive responsibility and one main reason to change?
  2. O — Open/Closed Can a new variant be added through an existing stable contract without needlessly breaking proven behaviour?
  3. L — Liskov Substitution Will behaviour remain correct when one implementation is replaced with another implementation of the same contract?
  4. I — Interface Segregation Is the interface small and shaped by consumer needs, or does it force the consumer to depend on unrelated capabilities?
  5. D — Dependency Inversion Do high-level business rules depend on a stable contract instead of a concrete technical detail?

Main principles

Meaning, example, and common misconception for every letter

The examples describe intent. In a particular project, the smallest sensible boundary may be larger or smaller.

SRP — Single Responsibility Principle

A class should have one cohesive responsibility, meaning one main kind of reason to change. OrderConfirmation can coordinate confirming an order, while generating a PDF invoice belongs elsewhere. This does not mean “one method per class” or splitting every line into its own object.

OCP — Open/Closed Principle

Code should be open for extension but closed against unnecessary disruption of stable behaviour. A new carrier can implement a meaningful contract instead of modifying branching everywhere. This does not mean existing code must never change; fixing a poor abstraction is often correct.

LSP — Liskov Substitution Principle

An implementation must preserve client expectations: it must not add stricter preconditions, withdraw a promised result, or violate an invariant. If DeliveryPriceQuote promises a price for an address, a replacement should not unexpectedly require an internal warehouse ID. Inheritance alone does not guarantee LSP.

ISP — Interface Segregation Principle

A consumer should not be forced to depend on methods it does not need. A pickup-point reader may require only findPoints(), not an entire administrative contract with deletion and synchronisation. ISP does not demand a single-method interface everywhere; division should follow different client needs.

DIP — Dependency Inversion Principle

High-level rules should not depend on low-level details; both should depend on an abstraction. A use case can know a contract for storing an order, while SQL or an ORM implements that contract. DIP is not dependency injection: DI is a common way to supply such a dependency.

Benefits and limitations

Good design is both clear and proportionate

Benefits

  • clearer placement of responsibility and less risk of collateral changes
  • contracts that can be tested or replaced deliberately
  • better support for variable parts such as carriers and payment gateways
  • concrete review questions instead of a vague request to “make it cleaner”

Limitations and common mistakes

  • an interface for every class without a second client or real contract
  • a premature strategy for a variant that does not yet exist
  • inheritance used only for code sharing when the contract meaning does not fit
  • using SOLID as an argument against a simple and understandable solution

Pragmatism

Abstraction should follow variability and risk, not doctrine.

If a project has one simple implementation and no concrete reason to replace it, a direct class may be clearer than a set of interfaces, factories, and adapters. The signal for change is not the number of lines, but repeated conditions, different client expectations, difficult testing, or a history of changes in the same place.

SOLID requires no particular number of layers or packages. The principles work as hypotheses: if a new boundary simplifies change and preserves clarity, it is useful. If it merely multiplies names and indirection, removing it is reasonable.

What to consider

Verify the actual contract and the cost of abstraction during a change

Healthy use of the principles rests on observable behaviour, not the number of files created.

  • name who will change the responsibility and why
  • test public contract behaviour with an alternative implementation as well
  • do not make replacement requirements stricter than the declared contract
  • split interfaces according to consumer needs, not mechanically by method
  • keep technical details at the boundary of the application rule
  • be able to explain which present or expected change every abstraction simplifies

Common questions

SOLID without unnecessary dogma

Must every class have only one method because of SRP?

No. SRP concerns cohesive responsibility and a reason to change, not the number of methods. A class can have several methods when together they express one understandable whole.

Does Open/Closed mean existing code must never change?

No. The principle warns against needless disruption of stable behaviour when adding a variant. Flawed or overly narrow code often needs revision; routing around it with another layer is not useful.

Is Dependency Inversion the same as Dependency Injection?

No. DIP is a principle directing dependencies towards an abstraction. Dependency injection is a technique for supplying an object that can support DIP, but it can also be used without a meaningful architectural boundary.

When should SOLID not be used?

It is not a technology that is switched on. For small, stable code, adding no new abstraction may be the most sensible choice. Use the principles as questions when a concrete change or dependency problem exists.

How I approach architecture

Code should protect changing rules and remain readable to the team.

In design, I distinguish stable rules from technical details and seek only boundaries worth their cost in relation to application complexity.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.