什么是SOLID原则
SOLID原则是指面向对象设计的五个原则,它们是:
Single Responsibility Principle(单一职责原则)
Open/Closed Principle(开放封闭原则)
Liskov Substitution Principle(里式替换原则)
Interface Segregation Principle(接口隔离原则)
Dependency Inversion Principle(依赖倒置原则)
单一职责原则
单一职责原则,也称单一功能原则,指的是一个类(或者模块、函数等)应该只负责一项职责,只有一个引起它变化的原因。该原则通常被认为是面向对象设计的开端,因为它的重点是将问题领域划分成小的单元,每个单元只负责一个职责。这有助于提高代码的可读性、可维护性和灵活性。
下面是一个不遵循单一职责原则的例子:
class Customer:
def __init__(self, name, address):
self.name = name
self.address = address
def place_order(self, order):
# some code to place the order
def make_payment(self, payment):
# some code to make the payment
def generate_invoice(self, invoice):
# some code to generate the invoice
在上面的例子中,一个Customer类负责了几件任务:下订单、支付账单、生成发票。这就违反了单一职责原则,因为这些任务应该被分配到不同的类中。
为了符合单一职责原则,我们可以将Customer类拆分成三个类──Order、Payment和Invoice。这些类只关心它们需要处理的对象,而不关心其它与它们无关的内容。下面是符合单一职责原则的代码:
class Order:
def __init__(self, customer, order_items):
self.customer = customer
self.order_items = order_items
def place_order(self):
# some code to place the order
class Payment:
def __init__(self, customer, payment_details):
self.customer = customer
self.payment_details = payment_details
def make_payment(self):
# some code to make the payment
class Invoice:
def __init__(self, customer, invoice_items):
self.customer = customer
self.invoice_items = invoice_items
def generate_invoice(self):
# some code to generate the invoice
这样代码更加清晰,每个类只负责自己的职责,将减少代码的复杂度。
开放封闭原则
开放封闭原则指的是软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着应该通过添加新的代码来扩展软件实体的行为,而不应该修改现有代码。
下面的代码展示了违反开放封闭原则的一个例子:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def set_width(self, width):
self.width = width
def set_height(self, height):
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
def set_width(self, width):
self.width = width
self.height = width
def set_height(self, height):
self.width = height
self.height = height
在上面的例子中,Square类继承自Rectangle类,但它重写了父类的set_width()和set_height()方法,破坏了原有的继承关系。如果在Rectangle类中添加了新的方法或者修改了现有的方法,Square类可能会受到影响。这就违反了开放封闭原则。
为了符合开放封闭原则,我们可以将代码重构为下面这样:
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
在上面的代码中,我们定义了一个Shape基类,Rectangle和Square都继承自Shape类。这样修改Rectangle类或者添加新的图形类都不会影响到Square类,而且代码也更加清晰和易于扩展。
里式替换原则
里式替换原则是指所有引用基类对象的地方必须能够透明地使用其子类的对象。这意味着,子类对象应该像基类对象一样可以被使用,在代码中不需要额外的逻辑判断。
下面是违反里式替换原则的一个例子:
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print('The animal is eating.')
class Fish(Animal):
def __init__(self, name):
super().__init__(name)
def eat(self):
print('The fish is eating.')
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
def eat(self):
print('The dog is eating.')
def feed(animal):
animal.eat()
fish = Fish('Nemo')
dog = Dog('Benji')
feed(fish)
feed(dog)
在上面的代码中,feed()函数使用了一个Animal对象作为参数,然而在调用时传入了Fish和Dog对象,这在语法上是合法的,但却违反了里式替换原则,因为Fish和Dog的eat()方法和Animal的eat()方法并不具有相同的行为。如果我们想让feed()函数能够接受Fish和Dog对象,只需要将它们的eat()方法重命名即可,这样它们就不再是Animal的子类,不再违反里式替换原则。
接口隔离原则
接口隔离原则是指使用多个专门的接口,而不是一个单一的规模庞大的接口。这有助于减少类之间的耦合性,提高系统的灵活性和可维护性。
下面是一个违反接口隔离原则的例子:
class Worker:
def work(self):
pass
def eat(self):
pass
class Technician(Worker):
def work(self):
print('Technician is working.')
def eat(self):
print('Technician is eating.')
class Manager(Worker):
def work(self):
print('Manager is working.')
def eat(self):
print('Manager is eating.')
class Cleaner(Worker):
def work(self):
print('Cleaner is working.')
def eat(self):
print('Cleaner is eating.')
class Robot(Worker):
def work(self):
print('Robot is working.')
worker = Technician()
manager = Manager()
cleaner = Cleaner()
robot = Robot()
worker.work()
worker.eat()
manager.work()
manager.eat()
cleaner.work()
cleaner.eat()
robot.work()
在上面的代码中,每个类都实现了work()和eat()方法,但是Robot类并不需要实现eat()方法,这违反了接口隔离原则。如果我们想移除Robot类中的eat()方法,我们就需要修改Robot类的代码,这样会破坏原有的代码,增加代码的复杂度。
为了符合接口隔离原则,我们可以将代码重构为下面这样:
class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Manager(Workable, Eatable):
def work(self):
print('Manager is working.')
def eat(self):
print('Manager is eating.')
class Technician(Workable, Eatable):
def work(self):
print('Technician is working.')
def eat(self):
print('Technician is eating.')
class Cleaner(Workable, Eatable):
def work(self):
print('Cleaner is working.')
def eat(self):
print('Cleaner is eating.')
class Robot(Workable):
def work(self):
print('Robot is working.')
worker = Technician()
manager = Manager()
cleaner = Cleaner()
robot = Robot()
worker.work()
worker.eat()
manager.work()
manager.eat()
cleaner.work()
cleaner.eat()
robot.work()
在上面的代码中,我们定义了一个Workable接口和一个Eatable接口,每个具体类只需要实现它们自己需要的接口,这样可以减少类之间的耦合性,提高系统的灵活性和可维护性。
依赖倒置原则
依赖倒置原则是指高层模块不应该依赖于底层模块,两者应该都依赖于抽象接口;抽象接口不应该依赖于具体实现,具体实现应该依赖于抽象接口。这意味着可以通过接口来将系统划分为更小、更具灵活性的模块,从而提高系统的可维护性、易于扩展。
下面是一个违反依赖倒置原则的例子:
class EmailService:
def send_email(self, to, subject, body):
# some code to send the email
class UserController:
def __init__(self):
self.email_service = EmailService()
def register_user(self, name, email):
# some code to register the user
self.email_service.send_email(email, 'Welcome to our site', 'Thank you for registering.')
在上面的代码中,UserController类依赖于EmailService类,我们无法将它们解耦。如果我们想更改或者移除EmailService类,我们就需要修改UserController类的代码,这样会破坏原有的代码,增加代码的复杂度。
为了符合依赖倒置原则,我们可以将代码重构为下面这样:
class Emailer:
def send_email(self, to, subject, body):
# some code to send the email
class IUserService:
def register_user(self, name, email):
pass
class UserService(IUserService):
def __init__(self, emailer):
self.emailer = emailer
def register_user(self, name, email):
# some code to register the user
self.emailer.send_email(email, 'Welcome to our site', 'Thank you for registering.')
emailer = Emailer()
user_service = UserService(emailer)
user_service.register_user('John', 'john@example.com')
在上面的代码中,我们定义了一个IUserService接口和一个UserService类,IUserService接口定义了register_user()方法,UserService类实现了该接口。UserController类只需要依赖于IUserService接口,而不依赖于UserService类本身,这样可以减少类之间的耦合性,提高系统的可维护性、易于扩展。