Improving Code Quality with SOLID Principles, Architecture Patterns, and Refactoring Techniques
This article explains how applying the five SOLID principles, choosing appropriate system architectures, using clear naming, drawing diagrams, and refactoring code—including guard clauses, responsibility‑chain patterns, and composition over inheritance—can dramatically improve Python program design and maintainability.
Why Good Code Matters
Writing maintainable, high‑quality code is essential for long‑term project health. The article outlines several key knowledge points that help improve program design.
Key Knowledge Points
Five basic SOLID principles of object‑oriented design
Three common architectural styles (monolithic, distributed, micro‑services)
Use of diagrams to clarify relationships
Choosing meaningful names for identifiers
Optimising nested if‑else structures
1. SOLID Principles
The five SOLID principles are:
Single Responsibility Principle
Open‑Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Composite Reuse (Composition) Principle
Single Responsibility Principle (SRP)
Example of a poorly structured function that reads a file, extracts data, and makes a network request:
import re
import requests
FILE = "./information.fet"
def extract(file):
fil = open(file, "r")
content = fil.read()
fil.close()
find_object = re.search(r"url=\d+", content)
find = find_object.group(1)
text = requests.get(find)
return text
if __name__ == "__main__":
text = extract(FILE)
print(text)Problems: no error handling, tightly coupled steps, and a single function does many things.
Refactored version splits responsibilities into small functions:
def get_source():
"""Get data source"""
return
def extract_(val):
"""Match key data"""
return
def fetch(val):
"""Send network request"""
return
def trim(val):
"""Trim data"""
return
def extract(file):
"""Extract target data"""
source = get_source()
content = extract_(source)
text = trim(fetch(content))
return text
if __name__ == "__main__":
text = extract(FILE)
print(text)Each function now does one thing, making the code easier to modify when requirements change.
Open‑Closed & Dependency Inversion
Typical storage class tightly couples business logic to a concrete MySQLSave implementation:
class MySQLSave:
def __init__(self):
pass
def insert(self):
pass
def update(self):
pass
class Business:
def __init__(self):
pass
def save(self):
saver = MySQLSave()
saver.insert()Using an abstract base class and dependency injection decouples the business layer from the storage implementation:
import abc
class Save(metaclass=abc.ABCMeta):
@abc.abstractmethod
def insert(self):
pass
@abc.abstractmethod
def update(self):
pass
class MySQLSave(Save):
def __init__(self):
self.classify = "mysql"
def insert(self):
pass
def update(self):
pass
class Excel(Save):
def __init__(self):
self.classify = "excel"
def insert(self):
pass
def update(self):
pass
class Business:
def __init__(self, saver):
self.saver = saver
def insert(self):
self.saver.insert()
def update(self):
self.saver.update()
if __name__ == "__main__":
mysql_saver = MySQLSave()
excel_saver = Excel()
business = Business(mysql_saver)The business code now depends on the stable abstraction Save, not on concrete storage classes.
Interface Segregation Principle (ISP)
Instead of a single large interface, split responsibilities. Example with a Book abstraction that separates purchase/borrow from shelving operations, allowing different client types to implement only the methods they need.
import abc
class Book(metaclass=abc.ABCMeta):
@abc.abstractmethod
def buy(self):
pass
@abc.abstractmethod
def borrow(self):
pass
@abc.abstractmethod
def shelf_off(self):
pass
@abc.abstractmethod
def shelf_on(self):
passComposite Reuse Principle (Composition over Inheritance)
Prefer object composition to inheritance to reduce coupling. The article shows a Car class and two derived classes, then refactors them to use a separate Color component that each car composes.
class Color:
pass
class KateCar:
color = Color()
def move(self):
pass
def engine(self):
pass
class FluentCar:
color = Color()
def move(self):
pass
def engine(self):
pass2. Common Architectural Styles
Three typical architectures are described:
Monolithic : all functionality in a single application; simple to deploy but suffers from high complexity, low deployment frequency, performance bottlenecks, and reliability issues.
Distributed : splits the system into multiple services to improve performance and reliability; still retains many monolithic drawbacks such as deployment complexity.
Micro‑services : many small, independently deployable services; offers better scalability, independent releases, and technology‑agnostic implementation, but introduces operational overhead, network latency, and coordination challenges.
The choice of architecture should be driven by concrete business needs and system complexity.
3. Supporting Practices
Diagramming : Use use‑case, collaboration, class, and state diagrams to visualise requirements, module relationships, and interactions.
Naming : Choose clear, consistent identifiers (e.g., avoid vague names like reversalList).
Optimising Nested if‑else : Apply guard clauses ("early return") to reduce nesting depth.
Example of guard‑clause optimisation:
if "http" not in url:
return
if "www" not in url:
returnResponsibility Chain Pattern
When many if‑else branches become unwieldy, replace them with a chain of handler objects. The article provides a Python implementation of a chain that decides which sales role can grant a discount based on price.
class Manager:
def __init__(self):
self.obj = None
def next_handler(self, obj):
self.obj = obj
def handler(self, price):
pass
class General(Manager):
def handler(self, price):
if price < 300000:
print(f"{price} 普通销售")
else:
self.obj.handler(price)
class Elite(Manager):
def handler(self, price):
if 300000 <= price < 800000:
print(f"{price} 精英销售")
else:
self.obj.handler(price)
class BOSS(Manager):
def handler(self, price):
if price >= 800000:
print(f"{price} 店长")
# Build the chain
general = General()
elite = Elite()
boss = BOSS()
general.next_handler(elite)
elite.next_handler(boss)
prices = [550000, 220000, 1500000, 200000, 330000]
for price in prices:
general.handler(price)This pattern decouples request senders from concrete handlers and makes the logic extensible.
Source: NightTeam – Wei Shidong
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
