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

\ 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?

Piyasa Fırsatı
Threshold Logosu
Threshold Fiyatı(T)
$0.00942
$0.00942$0.00942
-2.98%
USD
Threshold (T) Canlı Fiyat Grafiği
Sorumluluk Reddi: Bu sitede yeniden yayınlanan makaleler, halka açık platformlardan alınmıştır ve yalnızca bilgilendirme amaçlıdır. MEXC'nin görüşlerini yansıtmayabilir. Tüm hakları telif sahiplerine aittir. Herhangi bir içeriğin üçüncü taraf haklarını ihlal ettiğini düşünüyorsanız, kaldırılması için lütfen [email protected] ile iletişime geçin. MEXC, içeriğin doğruluğu, eksiksizliği veya güncelliği konusunda hiçbir garanti vermez ve sağlanan bilgilere dayalı olarak alınan herhangi bir eylemden sorumlu değildir. İçerik, finansal, yasal veya diğer profesyonel tavsiye niteliğinde değildir ve MEXC tarafından bir tavsiye veya onay olarak değerlendirilmemelidir.

Ayrıca Şunları da Beğenebilirsiniz

Trump-Backed WLFI Plunges 58% – Buyback Plan Announced to Halt Freefall

Trump-Backed WLFI Plunges 58% – Buyback Plan Announced to Halt Freefall

World Liberty Financial (WLFI), the Trump-linked DeFi project, is scrambling to stop a market collapse after its token lost over 50% of its value in September. On Friday, the project unveiled a full buyback-and-burn program, directing all treasury liquidity fees to absorb selling pressure. According to a governance post on X, the community approved the plan overwhelmingly, with WLFI pledging full transparency for every burn. The urgency of the move reflects WLFI’s steep losses in recent weeks. WLFI is trading Friday at $0.19, down from its September 1 peak of $0.46, according to CoinMarketCap, a 58% drop in less than a month. Weekly losses stand at 12.85%, with a 15.45% decline for the month. This isn’t the project’s first attempt at intervention. Just days after launch, WLFI burned 47 million tokens on September 3 to counter a 31% sell-off, sending the supply to a verified burn address. For World Liberty Financial, the buyback-and-burn program represents both a damage-control measure and a test of community faith. While tokenomics adjustments can provide short-term relief, the project will need to convince investors that WLFI has staying power beyond interventions. WLFI Launches Buyback-and-Burn Plan, Linking Token Scarcity to Platform Growth According to the governance proposal, WLFI will use fees generated from its protocol-owned liquidity (POL) pools on Ethereum, BNB Chain, and Solana to repurchase tokens from the open market. Once bought back, the tokens will be sent to a burn address, permanently removing them from circulation.WLFI Proposal Source: WLFI The project stressed that this system ties supply reduction directly to platform growth. As trading activity rises, more liquidity fees are generated, fueling larger buybacks and burns. This seeks to create a feedback loop where adoption drives scarcity, and scarcity strengthens token value. Importantly, the plan applies only to WLFI’s protocol-controlled liquidity pools. Community and third-party liquidity pools remain unaffected, ensuring the mechanism doesn’t interfere with external ecosystem contributions. In its proposal, the WLFI team argued that the strategy aligns long-term holders with the project’s future by systematically reducing supply and discouraging short-term speculation. Each burn increases the relative stake of committed investors, reinforcing confidence in WLFI’s tokenomics. To bolster credibility, WLFI has pledged full transparency: every buyback and burn will be verifiable on-chain and reported to the community in real time. WLFI Joins Hyperliquid, Jupiter, and Sky as Buyback Craze Spills Into Wall Street WLFI’s decision to adopt a full buyback-and-burn strategy places it among the most ambitious tokenomic models in crypto. While partly a response to its sharp September price decline, the move also reflects a trend of DeFi protocols leveraging revenue streams to cut supply, align incentives, and strengthen token value. Hyperliquid illustrates the model at scale. Nearly all of its platform fees are funneled into automated $HYPE buybacks via its Assistance Fund, creating sustained demand. By mid-2025, more than 20 million tokens had been repurchased, with nearly 30 million held by Q3, worth over $1.5 billion. This consistency both increased scarcity and cemented Hyperliquid’s dominance in decentralized derivatives. Other protocols have adopted variations. Jupiter directs half its fees into $JUP repurchases, locking tokens for three years. Raydium earmarks 12% of fees for $RAY buybacks, already removing 71 million tokens, roughly a quarter of the circulating supply. Burn-based models push further, as seen with Sky, which has spent $75 million since February 2025 to permanently erase $SKY tokens, boosting scarcity and governance influence. But the buyback phenomenon isn’t limited to DeFi. Increasingly, listed companies with crypto treasuries are adopting aggressive repurchase programs, sometimes to offset losses as their digital assets decline. According to a report, at least seven firms, ranging from gaming to biotech, have turned to buybacks, often funded by debt, to prop up falling stock prices. One of the latest is Thumzup Media, a digital advertising company with a growing Web3 footprint. On Thursday, it launched a $10 million share repurchase plan, extending its capital return strategy through 2026, after completing a $1 million program that saw 212,432 shares bought at an average of $4.71. DeFi Development Corp, the first public company built around a Solana-based treasury strategy, also recently expanded its buyback program to $100 million, up from $1 million, making it one of the largest stock repurchase initiatives in the digital asset sector. Together, these cases show how buybacks, whether in tokenomics or equities, are emerging as a key mechanism for stabilizing value and signaling confidence, even as motivations and execution vary widely
Paylaş
CryptoNews2025/09/26 19:12
Son of filmmaker Rob Reiner charged with homicide for death of his parents

Son of filmmaker Rob Reiner charged with homicide for death of his parents

FILE PHOTO: Rob Reiner, director of "The Princess Bride," arrives for a special 25th anniversary viewing of the film during the New York Film Festival in New York
Paylaş
Rappler2025/12/16 09:59
Bitcoin Peak Coming in 45 Days? BTC Price To Reach $150K

Bitcoin Peak Coming in 45 Days? BTC Price To Reach $150K

The post Bitcoin Peak Coming in 45 Days? BTC Price To Reach $150K appeared first on Coinpedia Fintech News Bitcoin has delivered one of its strongest performances in recent months, jumping from September lows of $108K to over $117K today. But while excitement is high, market watchers warn the clock is ticking.  History shows Bitcoin peaks don’t last forever, and analysts now believe the next major top could arrive within just 45 days, with …
Paylaş
CoinPedia2025/09/18 15:49