SOLID isn’t academic fluff - it’s your insurance against rage-quitting your own codebase. This article breaks down all five principles (SRP, OCP, LSP, ISP, DIP) with Python + UML, showing bad vs good examples. If you want cleaner design, fewer bugs, and teammates who don’t hate you, read this.SOLID isn’t academic fluff - it’s your insurance against rage-quitting your own codebase. This article breaks down all five principles (SRP, OCP, LSP, ISP, DIP) with Python + UML, showing bad vs good examples. If you want cleaner design, fewer bugs, and teammates who don’t hate you, read this.

SOLID Principles In Practice With Python And UML Examples in 2025

2025/08/28 14:22
10 min read

\ Let’s be real: most developers nod politely when SOLID comes up, then continue writing 500-line classes that smell worse than week-old fried eggs.

Here’s the thing: SOLID isn’t about pleasing your CS professor. It’s about writing code that your future self won’t want to rage-quit from after a single bugfix. Bad code means frustration, late nights, and way too much coffee.

SOLID is a language that senior devs use to communicate across years of maintenance. When you see it in a project, you don’t need to crawl line by line like some code archaeologist digging through ruins. Instead, you can predict the system’s behavior, understand responsibilities, and trust that classes aren’t hiding surprises like a clown in a sewer drain. Follow it, and you can read code like a map. Ignore it, and you're wandering blind through spaghetti caves.

What is SOLID (for the two people who missed the party)?

SOLID is an acronym for five object-oriented design principles coined by Uncle Bob Martin back when 2000s were still a thing:

  • S — Single Responsibility Principle (SRP)
  • O — Open–Closed Principle (OCP)
  • L — Liskov Substitution Principle (LSP)
  • I — Interface Segregation Principle (ISP)
  • D — Dependency Inversion Principle (DIP)

Each principle is simple in words, but game-changing in practice. Let’s walk through them with Python and UML examples, and see why your future code reviewers will thank you.

\


🪓 Single Responsibility Principle (SRP)

One job, one class. Don’t be that developer who mixes logging, DB writes, and business logic in one method.

Why it matters

If a class does too many things, every change risks breaking unrelated behavior. It’s like wiring your home’s electricity, plumbing, and gas through the same pipe - one adjustment and the whole house either floods, burns, or explodes.

Bad Example

Here’s a FileManager class that thinks it’s Batman: reads files, writes them, compresses them, and even appends greetings:

UML

from pathlib import Path from zipfile import ZipFile   class FileManager:     def __init__(self, file_path: str):         self.path = Path(file_path)      def read(self) -> str:         return self.path.read_text('utf-8')      def write(self, content: str):         self.path.write_text(content, 'utf-8')      def compress(self):         with ZipFile(self.path.with_suffix('.zip'), mode='w') as file:             file.write(self.path)      def add_greeting(self, content: str) -> str:         return content + '\nHello world!'   if __name__ == '__main__':     file_path = 'data.txt'     file_manager = FileManager(file_path)     content = file_manager.add_greeting(file_manager.read())     file_manager.write(content)     file_manager.compress() 

Call diagram

What’s wrong?

Although the usage looks easy, every time you need to tweak compression, you risk breaking file writing. Change greetings? Oops, now compression fails.

Good Example

Split responsibilities. Each class does one job, cleanly.

UML

from pathlib import Path from zipfile import ZipFile  DEFAULT_ENCODING = 'utf-8'   class FileReader:     def __init__(self, file_path: Path, encoding: str = DEFAULT_ENCODING):         self.path = file_path         self.encoding = encoding      def read(self) -> str:         return self.path.read_text(self.encoding)   class FileWriter:     def __init__(self, file_path: Path, encoding: str = DEFAULT_ENCODING):         self.path = file_path         self.encoding = encoding      def write(self, content: str):         self.path.write_text(content, self.encoding)   class FileCompressor:     def __init__(self, file_path: Path):         self.path = file_path      def compress(self):         with ZipFile(self.path.with_suffix('.zip'), mode='w') as file:             file.write(self.path)   def add_greeting(self, content: str) -> str:     return content + '\nHello world!'   if __name__ == '__main__':     file_path = Path('data.txt')     content = FileReader(file_path).read()     FileWriter(file_path).write(add_greeting(content))     FileCompressor(file_path).compress() 

Call diagram

Although the call diagram looks a little trickier, now you can modify compression without touching greetings. Future you approves.

\


🧩 Open–Closed Principle (OCP)

Code should be open for extension, closed for modification. Like Netflix - adding new shows without rewriting the entire app.

Why it matters

If every new feature forces you to edit the same class, your code becomes a fragile Jenga tower. Add one more method, and boom - production outage.

Bad Example

The classic “God class of geometry”:

UML

from math import pi   class Shape3D:     def __init__(self, shape_type: str, **kwargs):         self.shape_type = shape_type         self.kwargs = kwargs      def calculate_parallelepiped_volume(self):         return self.kwargs['w'] * self.kwargs['h'] * self.kwargs['l']      def calculate_sphere_volume(self):         return 3 / 4 * pi * self.kwargs['r'] ** 3      def calculate_cone_volume(self):         return 1 / 3 * pi * self.kwargs['r'] ** 2 * self.kwargs['h']      def volume(self) -> float:         if self.shape_type == 'parallelepiped':             return self.calculate_parallelepiped_volume()         if self.shape_type == 'sphere':             return self.calculate_sphere_volume()         if self.shape_type == 'cone':             return self.calculate_cone_volume()         # Add more ifs forever         raise ValueError   if __name__ == '__main__':     print(Shape3D('parallelepiped', w=1.0, h=2.0, l=3.0).volume())     print(Shape3D('sphere', r=3.5).volume())     print(Shape3D('cone', r=3.5, h=2.0).volume()) 

Every time you add a new shape, you hack this class. Here, we are not extending; we are modifying. This violates OCP.

Good Example

Make a common base class, extend it with new shapes.

UML

from abc import ABC, abstractmethod from math import pi   class Shape3D(ABC):     @abstractmethod     def volume(self) -> float:         raise NotImplementedError   class Parallelepiped(Shape3D):     def __init__(self, w: float, h: float, l: float):         self.w, self.h, self.l = w, h, l      def volume(self):         return self.w * self.h * self.l   class Sphere(Shape3D):     def __init__(self, r: float):         self.r = r      def volume(self):         return 3 / 4 * pi * self.r ** 3   class Cone(Shape3D):     def __init__(self, r: float, h: float):         self.r, self.h = r, h      def volume(self):         return 1 / 3 * pi * self.r ** 2 * self.h   if __name__ == '__main__':     print(Parallelepiped(w=1.0, h=2.0, l=3.0).volume())     print(Sphere(r=3.5).volume())     print(Cone(r=3.5, h=2.0).volume()) 

Want a Torus? Just new subclass. No if-hell required, no edits of old code. Clean. Extensible. Chef’s kiss.

\

🤔 Another Discussable Example of OCP Violation

Here’s a class that offers multiple hard-coded greeting methods:

UML

class GreetingContainer:     def __init__(self, name: str):         self.name = name      def hi_greet(self):         print("Hi, " + self.name)      def hey_greet(self):         print("Hey, " + self.name)      def hello_greet(self):         print("Hello, " + self.name) 

At first glance this looks fine: we’re not deleting or altering existing methods, we’re just adding new ones. Technically, we’re extending the class. But here’s the catch: clients using this class must know exactly which method to call, and the interface keeps bloating. This design is brittle - new greetings mean more methods and more places in the code that have to be aware of them. You can argue it’s not a direct OCP violation, but it drifts into LSP territory: instances of GreetingContainer can’t be treated uniformly, since behavior depends on which method you pick (we will discuss it a little later).

\


🦆 Liskov Substitution Principle (LSP)

If code expects an object of some type, any subclass should be usable without nasty surprises. A subclass that breaks this promise isn’t an extension - it’s a landmine disguised as a feature.

Why it matters

Subclasses must be replaceable with their children without breaking stuff. Violate this, and you’ll create runtime landmines.

Bad example

UML

class Bird:     def fly(self):         return "The bird is flying"      def walk(self):         return "The bird is walking"   class Duck(Bird):     def fly(self):         return "The duck is flying"      def walk(self):         return "The duck is walking"   class Penguin(Bird):     def fly(self):         raise AttributeError("Penguins cannot fly:(")      def walk(self):         return "The penguin is walking" 

Penguins do exist, but they break LSP here. Anywhere you expect a Bird, the substitution fails and the design no longer guarantees consistent behavior.

Good example

Split behaviors:

UML

class FlyingBird:     def fly(self):         return "The bird is flying"   class WalkingBird:     def walk(self):         return "The bird is walking"   class Duck(FlyingBird, WalkingBird):     def fly(self):         return "The duck is flying"      def walk(self):         return "The duck is walking"   class Penguin(WalkingBird):     def walk(self):         return "The penguin is walking" 

Now penguins waddle safely. Ducks still fly. Everyone’s happy. No runtime betrayals.

\

Good Example of a greeter from the OCP topic

Define a base Greeter and extend it with new variants:

UML

class Greeter:     def __init__(self, name: str):         self.name = name      def greet(self):         raise NotImplementedError   class HiGreeter(Greeter):     def greet(self):         print("Hi, " + self.name)   class HeyGreeter(Greeter):     def greet(self):         print("Hey, " + self.name)   class HelloGreeter(Greeter):     def greet(self):         print("Hello, " + self.name) 

This design is cleaner: the interface stays stable (greet()), new greetings are real extensions, and polymorphism works. Clients don’t care how the greeting is produced - they just call greet(). And the real benefit: we can swap one Greeter for another and remain confident the code behaves as expected, without digging into implementation details - because we followed OCP and LSP. That’s the spirit of predictable, maintainable design.

For the nerds, the formal definition of this principle is:

en.wikipedia.org/wiki/Liskov_substitution_principle

\


🔌 Interface Segregation Principle (ISP)

Don’t force a class to implement methods it doesn’t need. Otherwise you’re just padding the code with dead weight.

Why it matters

Clients shouldn’t be forced to implement methods they don’t need. If your interface has eat() and sleep(), but a robot only needs charge(), you’re designing like it’s 1999. Formally: many small, focused interfaces are always better than one bloated interface that forces irrelevant obligations.

Bad example

UML

from abc import ABC, abstractmethod   class IEmployee(ABC):     @abstractmethod     def work(self):         pass      @abstractmethod     def eat(self):         pass      @abstractmethod     def sleep(self):         pass   class Programmer(IEmployee):     def work(self):         print("Programmer programs programs")      def eat(self):         print("Programmer eats pizza")      def sleep(self):         print("Programmer falls asleep at 2 AM")   class Android(IEmployee):     def work(self):         print("Android moves boxes")      def eat(self):         raise NotImplementedError("Android doesn't eat, it's a machine")      def sleep(self):         raise NotImplementedError("Android doesn't sleep, it's a machine") 

Good example

Split interfaces:

UML

from abc import ABC, abstractmethod   class IEmployee(ABC):     @abstractmethod     def work(self):         pass   class IHuman(ABC):     @abstractmethod     def eat(self):         pass      @abstractmethod     def sleep(self):         pass   class IMachine:     @abstractmethod     def charge(self):         pass   class Programmer(IEmployee, IHuman):     def work(self):         print("Programmer programs programs")      def eat(self):         print("Programmer eats pizza")      def sleep(self):         print("Programmer falls asleep at 2 AM")   class Android(IEmployee, IMachine):     def work(self):         print("Android moves boxes")      def charge(self):         print("Android charges, wait for 3 hours") 

Now humans eat, robots charge. No unnecessary code. Interfaces are lean, not bloated.

\

\


🏗 Dependency Inversion Principle (DIP)

Depend on abstractions, not concrete implementations. Otherwise, your code is welded to a single library or vendor like shackles you’ll never escape from.

Why it matters

If your service directly imports boto3, good luck swapping S3 for HDFS, GCS, or even MinIO. From a formal perspective, this creates a tight coupling to a specific implementation rather than an abstraction, which severely limits extensibility and portability. Vendor lock-in = pain, and even a simple architectural decision - like supporting both on-prem HDFS and cloud object storage - suddenly requires massive rewrites.

Bad example

UML

import boto3   class DistributedFileSystem:     def __init__(self):         self.client = boto3.client('s3')      def read_file(path: str) -> bytes:         data = self.client.get_object(Key=path)         return data['Body'].read()      def write_file(path: str, file: bytes):         self.client.put_object(Body=file, Key=path) 

Direct AWS dependency. Migrating to another cloud = rewrite.

Good example

Abstract it:

UML

from abc import ABC, abstractmethod  import boto3   class DistributedFileSystem(ABC):     @abstractmethod     def read_file(path: str) -> bytes:         pass      @abstractmethod     def write_file(path: str, file: bytes):         pass   class BotoS3Client(DistributedFileSystem):     def __init__(self):         self.client = boto3.client('s3')      def read_file(path: str) -> bytes:         data = self.client.get_object(Key=path)         return data['Body'].read()      def write_file(path: str, file: bytes):         self.client.put_object(Body=file, Key=path)   class HDFSClient(DistributedFileSystem): ... 

Now you can switch storage backends without rewriting business logic. Future-proof and vendor-agnostic.

\


Final Thoughts: SOLID ≠ Religion, But It Saves Your Sanity

In 2025, frameworks evolve, clouds change, AI tools promise to “replace us”, but the pain of bad code is eternal.

SOLID isn’t dogma - it’s the difference between code that scales with your project and code nobody wants to work with.

Use SOLID not because Uncle Bob said so, but because your future self (and your team) will actually enjoy working with you.

\


👉 What do you think - are SOLID principles still relevant today, or do you prefer chaos-driven development?

Market Opportunity
Threshold Logo
Threshold Price(T)
$0.006718
$0.006718$0.006718
+3.29%
USD
Threshold (T) Live Price Chart
Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact crypto.news@mexc.com for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.

You May Also Like

North America Sees $2.3T in Crypto

North America Sees $2.3T in Crypto

The post North America Sees $2.3T in Crypto appeared on BitcoinEthereumNews.com. Key Notes North America received $2.3 trillion in crypto value between July 2024 and June 2025, representing 26% of global activity. Tokenized U.S. treasuries saw assets under management (AUM) grow from $2 billion to over $7 billion in the last twelve months. U.S.-listed Bitcoin ETFs now account for over $120 billion in AUM, signaling strong institutional demand for the asset. . North America has established itself as a major center for cryptocurrency activity, with significant transaction volumes recorded over the past year. The region’s growth highlights an increasing institutional and retail interest in digital assets, particularly within the United States. According to a new report from blockchain analytics firm Chainalysis published on September 17, North America received $2.3 trillion in cryptocurrency value between July 2024 and June 2025. This volume represents 26% of all global transaction activity during that period. The report suggests this activity was influenced by a more favorable regulatory outlook and institutional trading strategies. A peak in monthly value was recorded in December 2024, when an estimated $244 billion was transferred in a single month. ETFs and Tokenization Drive Adoption The rise of spot Bitcoin BTC $115 760 24h volatility: 0.5% Market cap: $2.30 T Vol. 24h: $43.60 B ETFs has been a significant factor in the market’s expansion. U.S.-listed Bitcoin ETFs now hold over $120 billion in assets under management (AUM), making up a large portion of the roughly $180 billion held globally. The strong demand is reflected in a recent resumption of inflows, although the products are not without their detractors, with author Robert Kiyosaki calling ETFs “for losers.” The market for tokenized real-world assets also saw notable growth. While funds holding tokenized U.S. treasuries expanded their AUM from approximately $2 billion to more than $7 billion, the trend is expanding into other asset classes.…
Share
BitcoinEthereumNews2025/09/18 02:07
The Critical Path To A Potential $10k Milestone

The Critical Path To A Potential $10k Milestone

The post The Critical Path To A Potential $10k Milestone appeared on BitcoinEthereumNews.com. Ethereum Price Prediction 2026-2030: The Critical Path To A Potential
Share
BitcoinEthereumNews2026/02/27 14:40
Priced Below $0.003, Google’s AI Says This is the Most Promising Crypto in 2025, Beating Solana (SOL)

Priced Below $0.003, Google’s AI Says This is the Most Promising Crypto in 2025, Beating Solana (SOL)

The post Priced Below $0.003, Google’s AI Says This is the Most Promising Crypto in 2025, Beating Solana (SOL) appeared on BitcoinEthereumNews.com. Little Pepe ($LILPEPE) may be the next cryptocurrency that investors are looking for to compete with Solana (SOL) and Ethereum (ETH). Google’s AI models say it’s the best choice for 2025. This meme-powered Layer 2 blockchain is currently in Stage 12 of its presale, with a cost of $0.0021. Traders, analysts, and meme coin fans are all interested in it. A Presale That’s Almost Sold Out Momentum for Little Pepe is undeniable. At the time of writing: Stage 12 Price: $0.0021 (Next Stage: $0.0022) USD Raised: $25.3 million / $25.4 million Tokens Sold: 15,692,215,448 / 15,750,000,000 Completion: 99.63% With only a fraction of tokens left before advancing to the next stage, early investors are racing to secure their positions. Once the presale ends, $LILPEPE will list on two major centralized exchanges (CEX) at launch, followed by listings on top decentralized exchanges with deep liquidity support. What is Unique about Little Pepe? Little Pepe is the world’s first Layer 2 blockchain, designed specifically for meme coins, offering a dedicated ecosystem where speed, security, and ultra-low fees are core component. Ultra-Fast & Cheap Transactions: Built to outpace Ethereum and even Solana in cost-efficiency. No Sniper Bots: Designed to keep trading fair and free from predatory bots. Utility-Powered Ecosystem: $LILPEPE is the lifeblood of the chain, powering everything from transfers to staking and participation on the launchpad. Zero Tax Policy: True DeFi freedom—no hidden buy/sell taxes. Little Pepe positions itself as a meme icon and an unstoppable kingdom for meme coin culture, where Pepe reigns supreme and innovation meets fun. Security First: The CertiK Audit Trust is critical in DeFi, and Little Pepe has taken steps to ensure investors feel secure. The project recently completed a CertiK audit, one of the industry’s gold standards for blockchain security. Audit Score: 95.49% Coverage Areas: Smart…
Share
BitcoinEthereumNews2025/09/19 05:40