Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

What are the SOLID principles in low-level design? They are five object-oriented design guidelines that shift attention from simply deciding which classes to create to deciding who owns each responsibility, what callers can rely on, and how likely changes should be accommodated. Used together with judgment, they can make evolving code easier to understand and change; applied mechanically, they can add abstractions without solving a real problem.

What SOLID changes about low-level design

Low-level design is more than naming classes and drawing their relationships. It is the work of deciding what objects should do, which responsibilities belong together, and which collaborators an object needs. Responsibility-driven design distributes those responsibilities across objects; SOLID gives you questions to test whether the distribution is likely to hold up as the software changes. The University of Bern’s lecture presents design methods as guidelines, not fixed rules: responsibility-driven design and object-oriented design.

The acronym names five principles: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. Their concerns are distinct: cohesion, safe extension, behavioral substitutability, focused interfaces, and the direction of dependencies. Together, they help answer questions that “Which class should I create?” does not answer on its own.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What each SOLID principle asks

Single Responsibility: who drives a change?

Keep a module focused around a coherent responsibility or actor that drives its changes. Robert C. Martin’s formulation, quoted by the SE Book, is: “A module should have one, and only one, reason to change.” That does not mean one method per class. It means unrelated reasons to change—such as business validation and receipt formatting—should not be forced to evolve inside one module simply because they occur in the same workflow. The SE Book’s discussion of SOLID also cautions against treating the principle as a mechanical class-size rule.

Open/Closed: where is likely variation handled?

Make likely variation possible through an extension point, rather than repeatedly editing stable policy whenever a new behavior appears. The concise definition attributed to Martin by Design Principles is: “Software entities should be open for extension, but closed for modification.” This is not a demand to anticipate every imaginable feature. First identify a credible variation; then decide whether an abstraction or composition point can contain it. Design Principles’ SOLID definitions.

Liskov Substitution: what can callers count on?

An implementation of a type should preserve the expectations callers rely on, so an alternative implementation can take its place without changing program correctness. A subtype that strengthens preconditions, weakens promised results, or behaves unexpectedly for valid calls may satisfy the language’s inheritance rules while breaking the design contract. The useful question is not simply “Is this class a subtype?” but “Can every client that uses the base contract safely use this implementation instead?” Design Principles attributes the principle’s definition to Martin.

Interface Segregation: what does this client actually need?

Give a client a focused interface containing the operations it uses, rather than making it depend on a broad general-purpose interface. A client that only needs to save an order should not also need to know about unrelated reporting or notification operations. Martin’s formulation, as presented by Design Principles, is: “Many client-specific interfaces are better than one general-purpose interface.” Examples and explanations are also available from SEforSDL.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Dependency Inversion: which way do dependencies point?

High-level policy should not depend directly on low-level infrastructure details when an abstraction can express the relationship. Instead, both can depend on an abstraction that represents what the policy needs. For example, an order workflow can depend on a persistence contract, while a database-specific adapter implements it. The abstraction should describe a useful policy-facing capability, not mirror every detail of a particular database. Design Principles attributes the summary “One should depend upon abstractions, rather than concrete implementations” to Martin; SEforSDL provides further examples.

Applying the principles to an order workflow

Imagine a workflow that validates a purchase, calculates its total, saves the order, and sends a receipt. Keeping all four jobs in one large class may seem convenient at first, but the design question is whether they change for the same reasons and whether each responsibility has a clear owner.

Separate responsibilities by reason to change

Validation rules and receipt presentation may be driven by different business concerns. Put each responsibility where its relevant changes belong, rather than creating a class for every verb in the workflow. The workflow can coordinate the sequence while delegating the work it does not own. This supports SRP without turning “one responsibility” into “one method.”

Keep the workflow independent of storage details

If the workflow constructs and calls a particular database client directly, a storage change can reach into business policy. A small persistence interface—perhaps an operation to save an order—lets the workflow depend on what it needs rather than on the database implementation. A database adapter supplies the implementation. This is Dependency Inversion; it can also make substitution for tests or other environments possible where that is a real need.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The interface has a cost: another abstraction and another indirection to understand. It earns its place when storage changes, substitution, or testing justify it. If none of those pressures exists and only one implementation is expected, introducing an interface solely to satisfy a checklist may make the design harder to follow.

Add extension points for credible variation

If the workflow must support multiple total-calculation policies, that may be a genuine variation point. A focused policy contract can let a new calculation be added without repeatedly rewriting stable orchestration. If there is only one straightforward calculation and no likely variation, an extension framework is speculative structure rather than a benefit. In either case, implementations must honor the behavior the workflow expects; otherwise the abstraction does not provide safe substitution.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How to judge whether a design is helping

Compare plausible designs against the actual change pressures, not against how many interfaces or classes each one contains.

  • Responsibility and cohesion: Do related changes land together, or do unrelated actors have to edit the same module?
  • Change cost: Can a likely new behavior be added at a clear extension point without risky edits to stable policy?
  • Substitutability: Can an alternate implementation honor the same contract without surprising its callers?
  • Interface scope: Does each client depend only on the operations it needs?
  • Dependency direction: Does core policy depend directly on infrastructure, or on a useful abstraction?
  • Abstraction cost: Does the flexibility address a real change, substitution, or testing need, or is it speculative?

SOLID is most useful when software is expected to change, multiple groups or actors request changes, or replacing dependencies matters for testing. It can be counterproductive for disposable prototypes, one-off scripts, simple value objects, and domains with only one implementation. The SE Book explicitly warns that applying SOLID without regard to simplicity can harm such cases: SE Book.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use SOLID as a set of design questions, not a scorecard

For each design decision, ask what changes, who owns the behavior, what a caller can safely expect, which operations a client actually needs, and whether policy is coupled to a replaceable detail. Then weigh the answer against the cost of adding structure. SOLID improves low-level design when its abstractions make real responsibilities and changes clearer—not when the code merely contains more classes.