面向对象设计的 SOLID 原则
什么是 SOLID?
SOLID 帮助您编写易于维护、扩展和理解的代码。
它是以下 5 项原则的首字母缩写:
S = Single-responsibility principle
O = Open-closed principle
L = Liskov substitution principle
I = Interface segregation principle
D = Dependency inversion principle
单一职责原则
- 一个类/模块应该只负责一件事。
- 一个类/模块应该只有一个被改变的原因。
下面是一个违反此原则的例子:
public class Customer {
public void add(Database db) {
try {
db.execute("INSERT INTO...");
} catch (Exception e) {
File.writeAllText("/var/log/error.log", e.toString());
}
}
}
该类Customer
负责写入数据库和写入日志文件。
- 如果我们想改变记录错误的方式,
Customer
就需要改变。 - 如果我们想改变写入数据库的方式,
Customer
就需要改变。
该代码应重构为:
class Customer {
private FileLogger logger = new FileLogger();
void add(Database db) {
try {
db.execute("INSERT INTO...");
} catch (Exception e) {
logger.log(e.toString());
}
}
}
class FileLogger {
void log(String error) {
File.writeAllText("/var/log/error.log", error);
}
}
其他需要单独类的示例包括:用户输入验证、身份验证、缓存。
注意不要过度碎片化代码(创建过多的职责)。记住,SOLID 的重点是让你的代码更易于维护。
进一步阅读:
开放封闭原则
- 类/模块应该对扩展开放,但对修改关闭。
- 通过添加新代码而不是更改现有代码来扩展功能。
- 目标是达到永远不会破坏系统核心的程度。
下面是一个违反此原则的例子:
public void pay(Request request) {
Payment payment = new Payment();
if (request.getType().eq("credit")) {
payment.payWithCreditCard();
} else {
payment.payWithPaypal();
}
}
public class Payment {
public void payWithCreditCard() {
// logic for paying with credit card
}
public void payWithPaypal() {
// logic for paying with paypal
}
}
如果我们想添加一种新的付款方式怎么办?我们必须修改该类Payment
,这违反了开放封闭原则。
该代码应重构为:
public void pay(Request request) {
PaymentFactory paymentFactory = new PaymentFactory();
payment = paymentFactory.initialisePayment(request.getType());
payment.pay();
}
//-----------------------
public class PaymentFactory {
public Payment intialisePayment(String type) {
if (type.eq("credit")) {
return new CreditCardPayment();
} elseif (type.eq("paypal")) {
return new PaypalPayment();
} elseif (type.eq("wire")) {
return new WirePayment();
}
throw new Exception("Unsupported payment method");
}
}
//-----------------------
interface Payment {
public void pay();
}
class CreditCardPayment implements Payment {
public void pay() {
// logic for paying with credit card
}
}
class PaypalPayment implements Payment {
public void pay() {
// logic for paying with paypal
}
}
class WirePayment implements Payment {
public void pay() {
// logic for paying with wire
}
}
现在我们可以通过添加新类来添加新的付款方式,而不是修改现有的类。
进一步阅读:
里氏替代原则
- 如果一个类实现了一个接口,它必须能够替代实现该接口的任何引用。
MySQL
例如,如果一个名为implements的类Database
,而另一个名为MongoDB
implements的类Database
,则您应该能够MySQL
用对象替换对象MongoDB
。
下面是一个违反此原则的例子:
public abstract class Bird {
public abstract void Fly();
}
public class Parrot : Bird {
public override void Fly() {
// logic for flying
}
}
public class Ostrich : Bird {
public override void Fly() {
// Can't implement as ostriches can't fly
throw new NotImplementedException();
}
}
上述设计很糟糕,因为Bird
假设所有的鸟都能飞。
然后可以将此代码重构为:
public abstract class Bird {
}
public abstract class FlyingBird : Bird {
public abstract void Fly();
}
public class Parrot : FlyingBird {
public override void Fly() {
// logic for flying
}
}
public class Ostrich : Bird {
}
该原则的要点是使用多态性和继承时要小心谨慎。
以下是这一原则的另一个更现实的遭遇:
BankAccount
你有一个带有方法的类withdrawal()
。所有银行账户都允许提款吗?例如,定期存款账户就不允许提款。
进一步阅读:
接口隔离原则
- 任何客户端都不应被迫依赖其不使用的方法。
下面是一个违反此原则的例子:
public interface Athlete {
void compete();
void swim();
void highJump();
void longJump();
}
public class JohnDoe implements Athlete {
@Override
public void compete() {
System.out.println("John Doe started competing");
}
@Override
public void swim() {
System.out.println("John Doe started swimming");
}
@Override
public void highJump() {
}
@Override
public void longJump() {
}
}
JohnDoe
只是一名游泳运动员,却被迫实施他永远不会使用的highJump
方法。longJump
然后可以将此代码重构为:
public interface Athlete {
void compete();
}
public interface SwimmingAthlete extends Athlete {
void swim();
}
public interface JumpingAthlete extends Athlete {
void highJump();
void longJump();
}
public class JohnDoe implements SwimmingAthlete {
@Override
public void compete() {
System.out.println("John Doe started competing");
}
@Override
public void swim() {
System.out.println("John Doe started swimming");
}
}
现在,JohnDoe
不必执行他没有能力执行的操作。
进一步阅读:
依赖倒置原则
- 高级模块不应该依赖于低级模块。它们应该依赖于抽象。
- 这使得您可以轻松地更改实现,而无需更改高级代码。
下面是一个违反此原则的例子:
public class BackEndDeveloper {
public void writeJava() {
}
}
public class FrontEndDeveloper {
public void writeJavascript() {
}
}
public class Project {
private BackEndDeveloper backEndDeveloper = new BackEndDeveloper();
private FrontEndDeveloper frontEndDeveloper = new FrontEndDeveloper();
public void implement() {
backEndDeveloper.writeJava();
frontEndDeveloper.writeJavascript();
}
}
该类Project
是一个高级模块,它依赖于诸如BackEndDeveloper
和 之类的低级模块FrontEndDeveloper
。这违反了该原则。
该代码应重构为:
public interface Developer {
void develop();
}
public class BackEndDeveloper implements Developer {
@Override
public void develop() {
writeJava();
}
private void writeJava() {
}
}
public class FrontEndDeveloper implements Developer {
@Override
public void develop() {
writeJavascript();
}
public void writeJavascript() {
}
}
//-----------------------
public class Project {
private List<Developer> developers;
public Project(List<Developer> developers) {
this.developers = developers;
}
public void implement() {
developers.forEach(d -> d.develop());
}
}
现在,该类Project
不依赖于较低级别的模块,而是依赖于抽象。
进一步阅读:
最后说明:不要太严格遵守 SOLID 原则
- SOLID 设计原则是原则,而不是规则。
- 应用 SOLID 时始终要运用常识(了解你的权衡)。
- 通常使用 SOLID,需要花费更多时间编写代码,因此您以后可以花费更少的时间阅读它。
- 最后,请记住将 SOLID 用作工具,而不是目标。