SOLID 是 5 种软件设计原则的首字母缩写,由美国的软件工程师 Robert C. Martin(习惯上被称为 Uncle Bob)总结。可以帮助程序员写出更加灵活、容易理解、可维护性强、方便扩展的健壮代码。
- 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),抽象不应该依赖于细节,细节应当依赖于抽象。即要针对抽象(接口)编程,而不是针对实现细节编程。
实例代码
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
def pay(self, payment_type, security_code):
if payment_type == "debit":
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
self.status = "paid"
elif payment_type == "credit":
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
self.status = "paid"
else:
raise Exception(f"Unknown payment type: {payment_type}")
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
order.pay("debit", "0372846")
上述 Python 代码实现了一个简单的“购物车”(订单)应用。
-
add_item
方法可以向订单中添加新的货物 -
total_price
方法可以计算订单的总价 -
pay
方法实现了订单的支付功能,支持借记卡、信用卡等支付方式
Single Responsibility Principle
单一职能原则。
将支付功能从 Order
类中分离出来,在另一个 PaymentProcessor
类中实现。同时去掉 pay
方法中的 if-else
判断,分别用两个函数 pay_debit
和 pay_credit
实现。
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
class PaymentProcessor:
def pay_debit(self, order, security_code):
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
def pay_credit(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
processor = PaymentProcessor()
processor.pay_debit(order, "0372846")
Open/Closed Principle
在最新的支付功能的实现中,如果我们需要添加一个新的支付方法(比如 PayPal),就必须修改 PaymentProcessor
类的原始代码。这就违反了 Open/Closed 原则,额外的功能应该通过扩展而不是修改原来的类来实现。
改进的方法是用一个基类(PaymentProcessor
)来定义基本的支付逻辑,再通过子类(如 DebitPaymentProcessor
)来实现具体的支付方法。这样每当添加一种新的支付方式,直接实现一个新的子类即可。
from abc import ABC, abstractmethod
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order, security_code):
pass
class DebitPaymentProcessor(PaymentProcessor):
def pay(self, order, security_code):
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
class CreditPaymentProcessor(PaymentProcessor):
def pay(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
processor = DebitPaymentProcessor()
processor.pay(order, "0372846")
Liskov Substitution Principle
假设我们现在需要添加一种新的支付方式 PayPalPaymentProcessor
,它在支付时并不依赖于 security_code
而是需要 email_address
进行验证。即 pay
方法的定义是 pay(self, order, email_address)
,与基类中虚拟方法的签名冲突。
改进的方法是将 pay
方法依赖的参数 security_code
或 email_address
移动到支付类的 __init__
方法中,将基类和子类的 pay
方法签名都改为 pay(self, order)
。
from abc import ABC, abstractmethod
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order):
pass
class DebitPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
def pay(self, order):
print("Processing debit payment type")
print(f"Verifying security code: {self.security_code}")
order.status = "paid"
class CreditPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
def pay(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
class PaypalPaymentProcessor(PaymentProcessor):
def __init__(self, email_address):
self.email_address = email_address
def pay(self, order):
print("Processing paypal payment type")
print(f"Verifying email address: {self.email_address}")
order.status = "paid"
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
processor = PaypalPaymentProcessor('hi@example.com')
processor.pay(order)
Interface Segregation Principle
假设我们需要在支付组件中添加一个验证短信的功能。直观的想法是直接在 PaymentProcessor
基类中添加一个 auth_sms
虚拟方法:
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order):
pass
@abstractmethod
def auth_sms(self, code):
pass
对于需要验证短信的支付方式比如借记卡,改为如下形式:
class DebitPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
self.verified = False
def auth_sms(self, code):
print(f"Verifying SMS code {code}")
self.verified = True
def pay(self, order):
if not self.verified:
raise Exception("Not authorized")
print("Processing debit payment type")
print(f"Verifying security code: {self.security_code}")
order.status = "paid"
对于不需要短信验证的支付方式比如信用卡,就改为如下形式:
class CreditPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
def auth_sms(self, code):
raise Exception(
"Credit card payments don't support SMS code authorization.")
def pay(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
上述实现的问题在于,我们定义了一个通用的支付接口(PaymentProcessor
),包含 pay
和 auth_sms
两种验证逻辑。但这两种逻辑并不总是被具体的支付方式(比如 CreditPaymentProcessor
)所需要。
这违反了接口分离原则。即接口的实现应该依赖于具体的客户端(子类)需求,而不能不管客户端是否需要,就将所有的功能都放在一个胖接口中。
可以额外再实现一个 PaymentProcessor_SMS
基类来定义短信验证的逻辑,让不需要短信验证的支付方式继承 PaymentProcessor
基类,需要短信验证的支付方式继承 PaymentProcessor_SMS
基类。
from abc import ABC, abstractmethod
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order):
pass
class PaymentProcessor_SMS(PaymentProcessor):
@abstractmethod
def auth_sms(self, code):
pass
class DebitPaymentProcessor(PaymentProcessor_SMS):
def __init__(self, security_code):
self.security_code = security_code
self.verified = False
def auth_sms(self, code):
print(f"Verifying SMS code {code}")
self.verified = True
def pay(self, order):
if not self.verified:
raise Exception("Not authorized")
print("Processing debit payment type")
print(f"Verifying security code: {self.security_code}")
order.status = "paid"
class CreditPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
def pay(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
class PaypalPaymentProcessor(PaymentProcessor_SMS):
def __init__(self, email_address):
self.email_address = email_address
self.verified = False
def auth_sms(self, code):
print(f"Verifying SMS code {code}")
self.verified = True
def pay(self, order):
if not self.verified:
raise Exception("Not authorized")
print("Processing paypal payment type")
print(f"Verifying email address: {self.email_address}")
order.status = "paid"
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
processor = PaypalPaymentProcessor('hi@example.com')
processor.auth_sms(123456)
processor.pay(order)
Composition over Inheritance
在软件设计的大部分场景中,组合要优于继承。因为继承总是意味着更紧密的耦合性。
实际上短信认证并不一定通过继承来实现(PaymentProcessor_SMS
),还可以通过组合来实现。
from abc import ABC, abstractmethod
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
class SMSAuth:
authorized = False
def verify_code(self, code):
print(f"Verifying code {code}")
self.authorized = True
def is_authorized(self) -> bool:
return self.authorized
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order):
pass
class DebitPaymentProcessor(PaymentProcessor):
def __init__(self, security_code, authorizer: SMSAuth):
self.security_code = security_code
self.authorizer = authorizer
self.verified = False
def pay(self, order):
if not self.authorizer.is_authorized():
raise Exception("Not authorized")
print("Processing debit payment type")
print(f"Verifying security code: {self.security_code}")
order.status = "paid"
class CreditPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
def pay(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
class PaypalPaymentProcessor(PaymentProcessor):
def __init__(self, email_address, authorizer: SMSAuth):
self.email_address = email_address
self.authorizer = authorizer
def pay(self, order):
if not self.authorizer.is_authorized():
raise Exception("Not authorized")
print("Processing paypal payment type")
print(f"Verifying email address: {self.email_address}")
order.status = "paid"
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
authorizer = SMSAuth()
processor = DebitPaymentProcessor('0372846', authorizer)
authorizer.verify_code(123456)
processor.pay(order)
定义一个 SMS_Auth
类来实现短信验证的逻辑,再通过组合的方式将其实例添加到具体的需要短信验证的支付方式中(比如 DebitPaymentProcessor
)。
Dependency Inversion Principle
细节应该依赖于抽象,而不是抽象依赖于细节。上述实现中就违反了这个原则。
比如借记卡支付方式(DebitPaymentProcessor
)的 __init__
方法,签名是 __init__(self, security_code, authorizer: SMSAuth)
。其中的 SMSAuth
是一个具体的短信验证类型,而不是一个通用的代表某种验证类型的抽象。
这样当支付方式需要的是另外一种验证方法(比如 NotARobot
),这里的签名就需要修改。
可以创建一个 Authorizer
基类来代表通用的验证方式,具体的验证方式比如 SMSAuth
、NotARobot
则作为 Authorizer
的子类来实现。
在支付方式的实现中,则使用 Authorizer
作为验证方式的类型定义。这样在使用支付类的实例时,就可以灵活地传入 Authorizer
的子类 SMSAuth
或者 NotARobot
进行组合。
from abc import ABC, abstractmethod
class Order:
items = []
quantities = []
prices = []
status = "open"
def add_item(self, name, quantity, price):
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
class Authorizer(ABC):
@abstractmethod
def is_authorized(self) -> bool:
pass
class SMSAuth(Authorizer):
authorized = False
def verify_code(self, code):
print(f"Verifying code {code}")
self.authorized = True
def is_authorized(self) -> bool:
return self.authorized
class NotARobot(Authorizer):
authorized = False
def not_a_robot(self):
print("Are you a robot? Naa")
self.authorized = True
def is_authorized(self) -> bool:
return self.authorized
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order):
pass
class DebitPaymentProcessor(PaymentProcessor):
def __init__(self, security_code, authorizer: Authorizer):
self.security_code = security_code
self.authorizer = authorizer
self.verified = False
def pay(self, order):
if not self.authorizer.is_authorized():
raise Exception("Not authorized")
print("Processing debit payment type")
print(f"Verifying security code: {self.security_code}")
order.status = "paid"
class CreditPaymentProcessor(PaymentProcessor):
def __init__(self, security_code):
self.security_code = security_code
def pay(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
class PaypalPaymentProcessor(PaymentProcessor):
def __init__(self, email_address, authorizer: Authorizer):
self.email_address = email_address
self.authorizer = authorizer
def pay(self, order):
if not self.authorizer.is_authorized():
raise Exception("Not authorized")
print("Processing paypal payment type")
print(f"Verifying email address: {self.email_address}")
order.status = "paid"
order = Order()
order.add_item("Keyborad", 1, 50)
order.add_item("SSD", 1, 150)
order.add_item("USB cable", 2, 5)
print(order.total_price())
authorizer = NotARobot()
processor = DebitPaymentProcessor('0372846', authorizer)
authorizer.not_a_robot()
processor.pay(order)
网友评论