设计模式的原则
单一职责原则
- 强调职责的分离,一个类,只需要负责一种职责即可
- 从职责的角度对类和接口进行划分
- 举例:
- main函数:职责是项目启动入口
- MVC框架:提倡接口层在Controller,服务层在service,持久层在Dao,划分不同的职责
接口隔离原则
- 强调接口使用的“精确性”和“最小化”:一个接口应该只包含客户端真正需要的方法。如果一个接口包含了客户端不需要的方法,就应该将这个接口拆分成更小、更具体的接口
- 主要体现在以下两个方面
- 不要使用没有任务依赖关系的接口
- 一个类对另一个类的依赖性应当是建立在最小的接口上的
反面案例
一个开关设备定义了 turnOn 、turnOff 、 print、scan 和 fax等五个方法。
public interface IDevice {
void turnOn();
void turnOff();
void print();
void scan();
void fax();
}
一个简单的开关设备只需要实现 turnOn 和turnOff方法即可,但是实现这个接口的实现类不得不实现所有方法
public class LightBulbImpl implements IDevice {
public void turnOn() {
// 实现开灯
}
public void turnOff() {
// 实现关灯
}
public void print() {
// 不需要实现
}
public void scan() {
// 不需要实现
}
public void fax() {
// 不需要实现
}
}
正面案例
按照接口隔离原则,将接口进行拆分成多个接口
public interface ISwitchable {
void turnOn();
void turnOff();
}
public interface IPrintable {
void print();
}
public interface IScannable {
void scan();
}
public interface IFaxable {
void fax();
}
可以按需根据java多实现的特点进行选择需要实现的方法。这样设计,每个类只依赖于他真正需要实现的接口
public class LightBulbImpl implements ISwitchable {
public void turnOn() {
// 实现开灯
}
public void turnOff() {
// 实现关灯
}
}
public class PrinterImpl implements ISwitchable, IPrintable {
public void turnOn() {
// 实现开打印机
}
public void turnOff() {
// 实现关打印机
}
public void print() {
// 实现打印
}
}
依赖倒置原则
通过依赖抽象接口,而不是具体的实现类,来解耦系统的高层模块和低层模块
反面案例
假设我们有一个简单的消息发送系统,其中 MessageSender
类负责发送消息,而 EmailSender
类则是具体的邮件发送实现:
public class EmailSender {
public void sendEmail(String message) {
// 发送邮件的具体实现
}
}
public class MessageSender {
private EmailSender emailSender;
public MessageSender() {
this.emailSender = new EmailSender();
}
public void send(String message) {
emailSender.sendEmail(message);
}
}
在这个设计中,MessageSender
直接依赖于 EmailSender
的具体实现。如果我们想要将邮件发送替换为短信发送或其他消息发送方式,就必须修改 MessageSender
类的代码,这样就违背了依赖倒置原则。
正面案例
引入一个抽象接口 MessageService
,然后 MessageSender
类依赖于这个接口,而不是具体的 EmailSender
实现:
public interface MessageService {
void sendMessage(String message);
}
public class EmailSender implements MessageService {
public void sendMessage(String message) {
// 发送邮件的具体实现
}
}
public class SmsSender implements MessageService {
public void sendMessage(String message) {
// 发送短信的具体实现
}
}
public class MessageSender {
private MessageService messageService;
public MessageSender(MessageService messageService) {
this.messageService = messageService;
}
public void send(String message) {
messageService.sendMessage(message);
}
}
在这个设计中,MessageSender
依赖于 MessageService
接口,而不是具体的实现类。通过这种方式,我们可以轻松替换消息发送的实现方式,比如从 EmailSender
切换到 SmsSender
,而不需要修改 MessageSender
类的代码。
public class Main {
public static void main(String[] args) {
MessageService emailService = new EmailSender();
MessageSender messageSender = new MessageSender(emailService);
messageSender.send("Hello via Email!");
MessageService smsService = new SmsSender();
messageSender = new MessageSender(smsService);
messageSender.send("Hello via SMS!");
}
}
里氏替换原则
- 在使用基类的地方能够透明地替换为其子类,而不会导致程序行为的变化
- 具体来说,里氏替换原则要求:
- 子类必须完全实现父类的方法:子类可以扩展父类的功能,但不能改变父类方法的行为。
- 子类的行为应该与父类保持一致:子类不能对父类已有的方法进行修改,尤其是不能破坏父类所承诺的功能。
反面案例
假设我们有一个 Rectangle
类表示矩形,并且一个 Square
类表示正方形。正方形可以被看作是矩形的一个特殊子类。
class Rectangle {
private int width;
private int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getArea() {
return width * height;
}
}
Square
继承了 Rectangle
,并且重写了 setWidth
和 setHeight
方法,确保正方形的宽和高相等。 但这里违反了里氏替换原则【子类不能对父类已有的方法进行修改,尤其是不能破坏父类所承诺的功能】
class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width); // 保证正方形的宽和高相等
}
@Override
public void setHeight(int height) {
super.setHeight(height);
super.setWidth(height); // 保证正方形的宽和高相等
}
}
public class Main {
public static void main(String[] args) {
Rectangle rect = new Rectangle();
rect.setWidth(5);
rect.setHeight(10);
System.out.println(rect.getArea()); // 输出 50
Rectangle square = new Square();
square.setWidth(5);
square.setHeight(10);
System.out.println(square.getArea()); // 输出 100
}
}
正面案例
Square
和 Rectangle
不应该有继承关系,因为它们在概念上并不完全相同,可以把它们实现为独立的类。
class Rectangle {
private int width;
private int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getArea() {
return width * height;
}
}
class Square {
private int side;
public void setSide(int side) {
this.side = side;
}
public int getSide() {
return side;
}
public int getArea() {
return side * side;
}
}
Rectangle
和 Square
是独立的,没有继承关系。这样,Rectangle
的行为不会因为 Square
的使用而发生意外的变化,符合里氏替换原则。
迪米特原则
一个对象应当尽可能少地了解其他对象,只与直接相关的对象进行通信,避免过度耦合。
开闭原则
核心思想是:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
- 对扩展开放:软件应该能够在不改变现有代码的情况下通过增加新功能来进行扩展。这意味着当需求变化时,可以通过扩展现有的代码来实现新功能,而不需要修改已有的功能。
- 对修改关闭:一旦一个软件模块被开发完成并投入使用,它应该尽可能避免修改。这可以减少因为修改代码引入新错误的风险。
反面案例
有一个 Shape
类,并且该类有一个方法 draw()
来绘制不同的形状。如果要添加新形状,需要修改 Shape
类,这就违反了开闭原则。
class Shape {
public void draw(String shapeType) {
if (shapeType.equals("Circle")) {
drawCircle();
} else if (shapeType.equals("Square")) {
drawSquare();
}
}
private void drawCircle() {
System.out.println("Drawing Circle");
}
private void drawSquare() {
System.out.println("Drawing Square");
}
}
正面案例
为了遵循开闭原则,可以使用多态和抽象类来设计系统。我们可以定义一个抽象的 Shape
类,并为每种形状创建一个子类。当需要添加新形状时,只需创建新的子类,无需修改现有的代码。
abstract class Shape {
abstract void draw();
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing Circle");
}
}
class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing Square");
}
}
class Triangle extends Shape {
@Override
void draw() {
System.out.println("Drawing Triangle");
}
}
class ShapeDrawer {
public void drawShape(Shape shape) {
shape.draw();
}
}
如果要添加一个新的形状(例如三角形),只需创建一个新的 Triangle
类,而不需要修改现有的 Shape
类和 ShapeDrawer
类的代码。
设计模式的分类
创建型模式
- 以“是否创建对象”为依据进行区分
- 包含以下五类
- 工厂方法模式
- 抽象工厂方法模式
- 单例模式
- 建造者模式
- 原型模式
结构型模式
- 注重类于对象的结合方式,将类或对象进行结合,形成更大的结构,不同组件扮演不同角色
- 包含以下七类
- 适配器模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 亨元模式
- 代理模式
行为型模式
- 注重设计模式所体现的行为动作
- 包含以下十一类
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式