何为设计原则?
我的理解,就是写代码的模板规范,按着这个规范写代码,可以提高代码的可读性、可复用性、可扩展性,总之一句话,提高代码质量。
五大设计原则(通常被称为SOLID原则)
先copy过来:
- 单一职责原则:一个类只负责一项职责(只有一个发生变化的原因)
- 开闭原则:对扩展开放,对修改封闭
- 里氏替换原则:基类适用的,子类一定适用(子类可以扩展父类的功能,但不能改变父类原有的功能)
- 依赖倒置原则:依赖抽象,不要依赖具体(面向接口编程)
- 接口隔离原则:使用多个专门的接口,而不要使用一个单一的(大)接口(接口单一职责)
设计原则一:单一职责原则
(Single Responsibility Principle, SRP)
一个类只负责一个功能
设计原则二:开闭原则
(Open-Closed Principle, OCP)
主要依赖于面向对象三大特性
graph LR
内部代码 --接口--> 外部代码
- 抽象(继承):定义一个稳定的抽象层,为不同的具体实现提供一个统一的接口。
- 封装变化:将系统中可能变化的部分封装起来,使外部代码不依赖于这些变化的细节。
- 多态:利用多态性,可以在运行时动态地选择不同的实现。
示例
// 抽象接口(用于外部代码调用)
public interface Shape {
void draw();
}
// 实现类1
public class Circle implements Shape {
public void draw() {
System.out.println("Drawing a circle.");
}
}
// 实现类2
public class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing a rectangle.");
}
}
// 外部代码调用(只需要调用抽象层)
public class ShapeDrawer {
public void drawShape(Shape shape) {
shape.draw();
}
}
// 若要扩展功能,可增加实现类3
public class Triangle implements Shape {
public void draw() {
System.out.println("Drawing a triangle.");
}
}
设计原则三:里氏替换原则
(Liskov Substitution Principle, LSP)
把基类所有使用的地方,换成子类,代码仍然可以正常运行
-
继承关系:
- 里氏替换原则强调了继承关系中父类与子类之间的关系。子类不应该改变父类的行为,而应该扩展父类的功能。
-
行为一致性:
- 子类的所有实例都应该能够替换掉它们的父类实例,且不影响程序的行为。这要求子类在行为上与父类保持一致。
-
扩展性:
- 子类可以添加新的方法或属性,但不应该覆盖或改变父类的已有方法的行为。
-
异常规则:
- 子类在方法实现中不应抛出父类未抛出的异常类型,除非是更具体的异常类型。
// 基类
public class Bird {
public void fly() {
System.out.println("The bird is flying");
}
}
// 不符合里氏替换原则,因为将用Bird的地方换为Sparrow后,调fly方法会抛异常
public class Sparrow extends Bird {
@Override
public void fly() {
throw new RuntimeException("Sparrow is too tired to fly");
}
}
// 符合里氏替换原则写法
public class Sparrow extends Bird {
@Override
public void fly() {
System.out.println("The Sparrow cannot flying");
}
}
设计原则四:依赖倒置原则
(Dependency Inversion Principle, DIP)
高层模块不应依赖于低层模块:即service可以依赖dao层,而不要dao层依赖service层 依赖抽象不应依赖于具体:即service层要注入dao层的抽象接口,而不是dao层具体实现类
设计原则五:接口隔离原则
(Interface Segregation Principle, ISP) 抽象接口的定义不能大而全,要合理定义接口
示例
假设我们有一个Animal接口,它包含了所有动物的共同特征:
public interface Animal {
void eat();
void sleep();
void fly();
}
现在,我们有一个Dog类,它实现了Animal接口:
public class Dog implements Animal {
public void eat() {
// 狗吃东西的逻辑
}
public void sleep() {
// 狗睡觉的逻辑
}
public void fly() {
// 狗不能飞,这里可以抛出异常或者返回
}
}
在这个例子中,Dog类实现了fly()方法,但实际上狗并不会飞。这违反了接口隔离原则,因为Dog类并不需要fly()方法。
为了遵循接口隔离原则,我们可以将Animal接口拆分为更细粒度的接口:
public interface Mammals {
void eat();
void sleep();
}
public interface FlyingAnimals {
void fly();
}
然后,我们让Dog类只实现Mammals接口:
复制
public class Dog implements Mammals {
public void eat() {
// 狗吃东西的逻辑
}
public void sleep() {
// 狗睡觉的逻辑
}
}