java设计模式
- 一、概述:
- 二、23种设计模式及其使用场景
- 2.1 创建型-单例模式
- 2.2 创建型-工厂方法模式(Factory Method)
- 2.3 创建型-抽象工厂模式(Abstract Factory)
- 2.4 创建型-建造者模式
- 2.5 创建型-原型模式(Prototype)
- 2.6 结构型-适配器模式(Adapter)
- 2.7 结构型-装饰器模式(Decorator)
- 2.8 结构型-代理模式
- 2.9 结构型-桥接模式
- 2.10 结构型-外观模式
- 2.11 结构型-组合模式
- 2.12 结构型-享元模式
- 2.13 行为型-策略模式
- 2.14 行为型-模板方法
- 2.15 行为型-责任链模式
- 2.16 行为型-观察者模式
- 2.17 行为型-访问者模式
- 2.18 行为型-中介者模式
- 2.19 行为型-命令模式
- 2.20 行为型-状态模式
- 2.21 行为型-备忘录模式
- 2.22 行为型-迭代器模式
- 2.23 行为型-解释器模式
一、概述:
1.1 概述
Java设计模式是一套在Java编程语言中常用的设计思想和模式,用于解决软件设计和开发过程中的常见问题。这些设计模式提供了一种结构化的方式,帮助开发人员设计出灵活、可维护和可扩展的代码。
Java设计模式主要遵循面向对象编程(OOP)的原则,如封装、继承、多态等,并以此为基础提供了一系列可复用的解决方案。设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性,描述了在不同情况下的代码组织、对象交互、类关系和行为分配等问题的最佳实践。
创建型模式(Creational Patterns):这些模式关注对象的创建过程,提供了创建对象的灵活性和可复用性。常见的创建型模式有单例模式、工厂模式、抽象工厂模式、建造者模式和原型模式
结构型模式(Structural Patterns):这些模式关注对象之间的组合和关联关系,用于构建更大的结构和类之间的关系。常见的结构型模式有适配器模式、桥接模式、装饰器模式、组合模式、外观模式、享元模式和代理模式。
行为型模式(Behavioral Patterns):这些模式关注对象之间的交互和职责分配,用于定义对象之间的通信和协作方式。常见的行为型模式有模板方法模式、策略模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。
并发型模式(Concurrency Patterns):这些模式关注多线程环境下的并发控制和线程安全性。常见的并发型模式有线程池模式、生产者-消费者模式、读写锁模式、信号量模式、屏障模式等。
J2EE模式(J2EE Patterns):这些模式是特定于Java企业版(J2EE)的模式,用于解决企业级应用程序开发中的常见问题。常见的J2EE模式有MVC模式、业务代表模式、数据访问对象模式、前端控制器模式、数据传输对象模式等
1.2 设计原则
单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起它变化的原因。每个类应该专注于单一的责任或功能。
开闭原则(Open-Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。应通过扩展现有代码来实现新功能,而不是修改原有代码。
里氏替换原则(Liskov Substitution Principle,LSP):子类应该能够替换其父类,并且不影响程序的正确性。子类应该遵循父类定义的契约和行为。
依赖倒置原则(Dependency Inversion Principle,DIP):依赖于抽象而不是具体实现。高层模块不应该依赖于低层模块的细节,而应该依赖于抽象。
接口隔离原则(Interface Segregation Principle,ISP):客户端不应该强迫依赖于它们不需要的接口。应该将接口细分为更小、更具体的接口,以满足客户端的需求。
迪米特法则(Law of Demeter,LoD):一个对象应该尽可能少地与其他对象发生相互作用。一个类应该对其他类知道得越少越好,只与直接的朋友进行通信
二、23种设计模式及其使用场景
2.1 创建型-单例模式
单例设计模式是一种创建型设计模式,用于确保类只有一个实例,并提供全局访问点。
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数,防止外部实例化
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// 其他方法和属性...
}
在这个例子中,Singleton类只有一个私有静态变量instance,它保存类的唯一实例。构造函数被声明为私有,以防止其他类通过new关键字实例化该类。而getInstance()方法是公有静态方法,它负责返回Singleton类的唯一实例。
该实现中使用了懒加载策略,即在首次调用getInstance()方法时才创建实例。为了确保多线程环境下的线程安全性,getInstance()方法被声明为synchronized,以保证同一时间只有一个线程可以访问该方法。
单例模式适用于以下场景:
当类的实例只需要存在一个时,例如配置信息类、日志记录类等。
当需要在系统中共享资源时,例如数据库连接池、线程池等。
当某个对象需要被频繁访问和操作,但又不希望频繁创建新实例时,例如缓存机制的实现。
需要注意的是,单例模式虽然提供了全局访问点,但也可能导致全局状态的管理困难,增加了代码的耦合性。因此,在使用单例模式时需要谨慎考虑其适用性,并注意避免滥用。
2.1.1 单例模式的类型
单例模式有两种类型:
懒汉式:在真正需要使用对象时才去创建该单例类对象
饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
2.1.1.1 懒汉式创建单例对象
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2.1.1.2 饿汉式创建单例对象
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
public class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
}
2.1.1.3 懒汉式如何保证只创建一个对象
我们看下懒汉式主要代码片段
下面展示一些 内联代码片
。
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
这个方法其实是存在问题的,试想一下,如果两个线程同时判断singleton为空,那么它们都会去实例化一个Singleton对象,这就变成双例了。所以,我们要解决的是线程安全问题。
最容易想到的解决方法就是在方法上加锁,或者是对类对象加锁,程序就会变成下面这个样子
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
或者
public static Singleton getInstance() {
synchronized(Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
这样就规避了两个线程同时创建Singleton对象的风险,但是引来另外一个问题:每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。
接下来要做的就是优化性能,目标是:如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例
所以直接在方法上加锁的方式就被废掉了,因为这种方式无论如何都需要先获取锁
public static Singleton getInstance() {
if (singleton == null) { // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
singleton = new Singleton();
}
}
}
return singleton;
}
上面的代码已经完美地解决了并发安全+性能低效问题:
第2行代码,如果singleton不为空,则直接返回对象,不需要获取锁;而如果多个线程中发现singleton为空,则进入分支;
第3行代码,多个线程尝试争抢同一个锁,只有一个线程争抢成功,第一个获取到锁的线程会再次判断singleton是否为空,因为singleton有可能已经被之前的线程实例化
其它之后获取到锁的线程在执行到第4行校验代码,发现singleton已经不为空了,则不会再new一个对象,直接返回对象即可
之后所有进入该方法的线程都不会去获取锁,在第一次判断singleton对象时已经不为空了
因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)
这种机制主要包含三个步骤:
1、检查实例是否已经被创建,如果已经创建,则直接返回实例。
2、如果实例尚未创建,则使用同步锁来确保只有一个线程可以进入临界区。
3、在临界区内再次检查实例是否已经被创建,如果没有,则创建实例并将其赋值给相应的变量。
完整的代码如下所示:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) { // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
singleton = new Singleton();
}
}
}
return singleton;
}
}
2.1.1.4 使用volatile防止指令重排
先说一下什么是指令重排(具体关于指令重拍的知识参考我其他文章):
为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,并确保这一结果和顺序执行结果是一致的,但是这个过程并不保证各个语句计算的先后顺序和输入代码中的顺序一致。这就是指令重排序。
简单来说,就是指你在程序中写的代码,在执行时并不一定按照写的顺序。
在Java中,JVM能够根据处理器特性(CPU多级缓存系统、多核处理器等)适当对机器指令进行重排序,最大限度发挥机器性能。
Java中的指令重排序有两次,第一次发生在将字节码编译成机器码的阶段,第二次发生在CPU执行的时候,也会适当对指令进行重排。
然而,在上述的代码中,指令重排可能会影响到懒汉单例模式的正确性。在没有足够的同步机制的情况下,编译器或处理器可能会对上述步骤进行重排,导致多个线程可能在实例尚未完全初始化时访问到未初始化的实例。
为了解决指令重排带来的问题,可以使用volatile关键字对实例变量进行修饰。在Java中,将实例变量声明为volatile可以确保对象的创建和初始化操作不会被重排,从而避免懒汉单例模式的线程安全性问题。
使用volatile关键字修饰的实例变量会在每次访问时都从主内存中读取,而不是使用缓存值。这样可以保证多个线程对实例的访问都能看到最新的初始化状态,避免了指令重排可能带来的问题
最终代码如下所示:
public class Singleton {
private static volatile Singleton singleton;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) { // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
singleton = new Singleton();
}
}
}
return singleton;
}
}
2.2 创建型-工厂方法模式(Factory Method)
定义一个用于创建对象的接口,但将实际的创建工作推迟到子类。
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public interface ShapeFactory {
Shape createShape();
}
public class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
public class SquareFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Square();
}
}
public class Main {
public static void main(String[] args) {
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
ShapeFactory squareFactory = new SquareFactory();
Shape square = squareFactory.createShape();
square.draw();
}
}
**优点:**将对象的创建与使用分离,符合开闭原则,易于扩展。
**缺点:**每个具体产品都需要一个具体工厂类,增加了类的个数。
**使用场景:**当对象的创建逻辑比较复杂,或者存在多种不同类型的对象需要创建时,可使用工厂方法模式
2.3 创建型-抽象工厂模式(Abstract Factory)
提供一个创建一系列相关或相互依赖对象的接口,而无需指定其具体类。
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public interface ShapeFactory {
Shape createShape();
}
public class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
public class SquareFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Square();
}
}
public class Main {
public static void main(String[] args) {
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
ShapeFactory squareFactory = new SquareFactory();
Shape square = squareFactory.createShape();
square.draw();
}
}
优点:将对象的创建与使用分离,符合开闭原则,易于扩展;可以确保创建的对象是相互关联或依赖的产品族。
缺点:增加了系统的抽象性和复杂性;难以支持新种类的产品。
使用场景:当需要创建一系列相互关联或依赖的对象时,可使用抽象工厂模式
2.4 创建型-建造者模式
将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
public class Product {
private String part1;
private String part2;
// ...
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
// ...
}
public interface Builder {
void buildPart1();
void buildPart2();
// ...
Product getResult();
}
public class ConcreteBuilder implements Builder {
private Product product;
public ConcreteBuilder() {
this.product = new Product();
}
@Override
public void buildPart1() {
product.setPart1("Part 1");
}
@Override
public void buildPart2() {
product.setPart2("Part 2");
}
// ...
@Override
public Product getResult() {
return product;
}
}
public class Director {
private Builder builder;
public void setBuilder(Builder builder) {
this.builder = builder;
}
public Product construct() {
builder.buildPart1();
builder.buildPart2();
// ...
return builder.getResult();
}
}
public class Main {
public static void main(String[] args) {
Director director = new Director();
Builder builder = new ConcreteBuilder();
director.setBuilder(builder);
Product product = director.construct();
}
}
优点:将复杂对象的构建过程封装在具体的建造者类中;易于扩展和维护;可以改变产品的内部表示。
缺点:增加了系统的复杂性;要求建造者必须知道产品的每个细节。
使用场景:当需要创建复杂对象,并且希望将其构建过程与表示分离时,可使用建造者模式
2.5 创建型-原型模式(Prototype)
通过复制现有对象来创建新对象,而不是通过实例化类
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType() {
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
protected Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
public class Circle extends Shape {
public Circle() {
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class Square extends Shape {
public Square() {
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class ShapeCache {
private static Map<String, Shape> shapeMap = new HashMap<>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(), circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(), square);
}
}
public class Main {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape1 = ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape1.getType());
Shape clonedShape2 = ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
}
}
优点:通过复制现有对象来创建新对象,避免了实例化操作的开销;可以动态添加或删除原型。
缺点:每个原型类都必须实现克隆方法;深克隆可能比较复杂。
使用场景:当需要创建一个与现有对象类似的对象,或者创建预定义的原型对象,可使用原型模式
2.6 结构型-适配器模式(Adapter)
将一个类的接口转换成客户希望的另一个接口,使得原本不兼容的类可以在一起工作,将目标类和适配者类解耦;同时也符合“开闭原则”,可以在不修改原代码的基础上增加新的适配器类;
public interface MediaPlayer {
void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: " + fileName);
}
@Override
public void playMp4(String fileName) {
// do nothing
}
}
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// do nothing
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: " + fileName);
}
}
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMediaPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMediaPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMediaPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMediaPlayer.playMp4(fileName);
}
}
}
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
} else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
public class Main {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("vlc", "movie.vlc");
audioPlayer.play("mp4", "video.mp4");
audioPlayer.play("avi", "movie.avi");
}
}
优点:通过适配器,客户可以调用同一接口进行不同类型的操作;解耦了目标和源之间的耦合。
缺点:增加了类的个数;不适合在设计初期就使用,而是作为一个已有系统的扩展。
使用场景:当需要将一个类的接口转换成客户希望的另一个接口时,可使用适配器模式。
2.7 结构型-装饰器模式(Decorator)
动态地给一个对象添加额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void draw() {
decoratedShape.draw();
}
}
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder();
}
private void setRedBorder() {
System.out.println("Border Color: Red");
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
Shape redCircle = new RedShapeDecorator(new Circle());
circle.draw();
System.out.println();
redCircle.draw();
}
}
优点:动态地给一个对象添加额外的职责,而无需生成子类;可以对对象进行多次装饰。
缺点:装饰器模式增加了许多小对象,影响系统的性能;使得设计变得复杂。
使用场景:当需要动态地给对象添加功能,且对继承关系有限制时,可使用装饰器模式
2.8 结构型-代理模式
结构型-代理模式是一种设计模式,它允许通过创建代理对象来控制对另一个对象的访问。代理对象充当被代理对象的中间人,客户端通过代理对象与被代理对象进行交互,而不是直接访问被代理对象
# 定义一个共同的接口
class Subject:
def request(self):
pass
# 实现被代理对象
class RealSubject(Subject):
def request(self):
print("RealSubject: 处理请求")
# 实现代理对象
class Proxy(Subject):
def __init__(self, real_subject):
self.real_subject = real_subject
def request(self):
if self.check_access():
self.real_subject.request()
self.log_access()
def check_access(self):
print("Proxy: 验证访问权限")
# 执行一些访问权限验证的逻辑
return True
def log_access(self):
print("Proxy: 记录访问日志")
# 客户端使用代理对象
def client_code(subject):
subject.request()
# 创建被代理对象
real_subject = RealSubject()
# 创建代理对象
proxy = Proxy(real_subject)
# 客户端通过代理对象访问被代理对象
client_code(proxy)
代理模式的优点包括:
能够实现对目标对象的保护,客户端无需直接访问目标对象,而是通过代理进行间接访问,可以控制访问权限。
可以在不修改目标对象的情况下增加额外的功能,例如记录日志、缓存等。
可以实现对目标对象的远程访问,通过代理对象与远程对象进行通信。
代理模式的缺点包括:
增加了系统的复杂性,引入了额外的代理类。
可能会导致请求的处理速度变慢,因为要通过代理转发请求。
代理模式适用于以下场景:
需要对目标对象进行访问控制或保护的情况,例如权限验证、安全检查等。
需要在访问目标对象之前或之后执行一些额外操作的情况,例如记录日志、缓存数据等。
需要实现对目标对象的远程访问的情况,例如网络通信。
2.9 结构型-桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,它通过将抽象部分与其实现部分分离,使它们可以独立地变化。这种模式的核心思想是将一个大类或一组类拆分为抽象和实现两个独立的层次结构,使它们可以独立地进行扩展和修改。
在桥接模式中,抽象部分(Abstraction)定义了高层的抽象接口,它包含了抽象类和一些定义了操作的方法。实现部分(Implementation)则定义了低层的实现接口,它也是一个抽象类或接口。通过将抽象部分与实现部分进行桥接,可以在运行时动态地将它们组合起来
// 定义实现部分的接口
interface Implementor {
void operationImpl();
}
// 具体实现类A
class ConcreteImplementorA implements Implementor {
public void operationImpl() {
System.out.println("ConcreteImplementorA operation");
}
}
// 具体实现类B
class ConcreteImplementorB implements Implementor {
public void operationImpl() {
System.out.println("ConcreteImplementorB operation");
}
}
// 定义抽象部分的接口
abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
abstract void operation();
}
// 具体抽象类
class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
public void operation() {
implementor.operationImpl();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Implementor implementorA = new ConcreteImplementorA();
Implementor implementorB = new ConcreteImplementorB();
Abstraction abstractionA = new RefinedAbstraction(implementorA);
abstractionA.operation();
Abstraction abstractionB = new RefinedAbstraction(implementorB);
abstractionB.operation();
}
}
优点:
桥接模式将抽象部分与实现部分分离,使得它们可以独立地变化,增强了系统的灵活性。
桥接模式提供了一种扩展机制,使得抽象部分和实现部分可以独立地进行扩展和修改,而不会相互影响。
桥接模式符合开闭原则,可以在不修改已有代码的情况下引入新的抽象和实现。
缺点:
桥接模式增加了系统的复杂性,因为需要创建抽象部分和实现部分之间的关联关系。
如果抽象部分和实现部分的数量庞大,将会增加管理和维护的难度。
使用场景:
当一个类有多个变化维度时,可以使用桥接模式将各个维度的变化分离,使得它们可以独立地进行扩展和修改。
当需要在抽象部分和实现部分之间增加更多的灵活性时,可以使用桥接模式。例如,如果希望在运行时动态地将抽象部分和实现部分进行组合。
当希望通过继承来扩展抽象部分或实现部分时,可以使用桥接模式。这样可以避免类爆炸的问题,将各个维度的变化分离开来
2.10 结构型-外观模式
结构型-外观模式是一种设计模式,它提供了一个简化复杂系统的统一接口。它隐藏了系统的复杂性,并为客户端提供一个高层次的接口,使得客户端与系统的交互变得更加简单。
外观模式的核心概念是通过一个外观类(Facade Class)来封装一组子系统的接口,将客户端与子系统的交互转移给外观类来处理。客户端只需要与外观类进行交互,而不需要直接与子系统的各个类进行交互。
// 子系统A
class SubsystemA {
public void operationA() {
System.out.println("SubsystemA: operation A");
}
}
// 子系统B
class SubsystemB {
public void operationB() {
System.out.println("SubsystemB: operation B");
}
}
// 外观类
class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
}
public void operation() {
subsystemA.operationA();
subsystemB.operationB();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation();
}
}
优点:
简化客户端与子系统之间的交互,减少了客户端的复杂性。
隐藏了子系统的具体实现细节,提高了系统的安全性和稳定性。
降低了子系统的耦合度,使得子系统的修改和扩展更加容易。
缺点:
如果系统变得过于复杂,外观类可能会变得庞大,难以维护。
外观模式的使用可能会限制了一些特定的需求,因为它提供的是一个简化的接口,不能满足所有的情况。
使用场景:
当存在一个复杂的系统,并且希望简化与该系统的交互时,可以考虑使用外观模式。
当希望封装子系统,提供一个更高层次的接口给客户端时,可以使用外观模式。
当需要对子系统进行重构时,可以使用外观模式来提供向后兼容性,使得客户端的代码不需要进行修改。
外观模式可以帮助开发者组织复杂的代码结构,提高系统的可维护性和可扩展性。然而,适用于外观模式的场景需要谨慎选择,避免过度使用,导致不必要的复杂性。
2.11 结构型-组合模式
结构型设计模式中的组合模式是一种将对象组合成树状结构以表示"部分-整体"层次关系的模式。它使得客户端可以统一处理单个对象和组合对象,而无需区分它们之间的差异。
import java.util.ArrayList;
import java.util.List;
// 组件接口
interface Component {
void operation();
}
// 叶子节点类
class Leaf implements Component {
@Override
public void operation() {
System.out.println("执行叶子节点操作");
}
}
// 组合节点类
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
System.out.println("执行组合节点操作");
for (Component component : children) {
component.operation();
}
}
}
// 客户端使用示例
public class Client {
public static void main(String[] args) {
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
Component composite1 = new Composite();
composite1.add(leaf1);
composite1.add(leaf2);
Component composite2 = new Composite();
composite2.add(composite1);
composite2.operation();
}
}
上述代码中,Component是组件的接口,Leaf是叶子节点类,Composite是组合节点类。Composite可以包含其他Component对象,并通过add和remove方法来管理子节点。在operation方法中,组合节点执行自己的操作,并递归调用子节点的操作。
组合模式的优点包括:
简化客户端代码:客户端可以统一对待单个对象和组合对象,无需区分它们的类型。
可扩展性:可以很方便地增加新的叶子节点或组合节点,扩展树状结构。
灵活性:可以通过组合不同的对象形成不同的结构,满足不同的需求。
组合模式的缺点包括:
可能导致设计过度一般化:当组件类的接口过于抽象时,可能会导致系统复杂性增加。
对象职责模糊:组合模式中,叶子节点和组合节点具有相同的接口,但其职责可能不同,需要注意区分。
使用场景:
当需要表示对象的部分-整体层次结构,并希望能够一致地处理整体和部分时,可以使用组合模式。
当希望客户端忽略组合对象和单个对象的差异,统一地使用它们时,可以使用组合模式。
当需要对对象进行嵌套组合,形成树状结构,并能够以递归方式处理对象时,可以使用组合模式。
2.12 结构型-享元模式
结构型-享元模式是一种设计模式,它通过共享对象来减少系统中相似对象的数量,从而提高系统的性能和效率。
概念:
享元模式将对象分为两种:内部状态(Intrinsic State)和外部状态(Extrinsic State)。内部状态是对象可共享的部分,它不会随着对象的上下文变化而改变,而外部状态则是对象特定的上下文信息。享元模式通过共享内部状态,减少了相似对象的数量,并在需要时通过传递外部状态来区分对象。
import java.util.HashMap;
import java.util.Map;
interface WebsitePage {
void render(String title, String content);
}
class ConcretePage implements WebsitePage {
private String sharedNavbar;
private String sharedFooter;
public ConcretePage(String sharedNavbar, String sharedFooter) {
this.sharedNavbar = sharedNavbar;
this.sharedFooter = sharedFooter;
}
public void render(String title, String content) {
System.out.println("Rendering page:");
System.out.println(sharedNavbar);
System.out.println("Title: " + title);
System.out.println("Content: " + content);
System.out.println(sharedFooter);
System.out.println();
}
}
class WebsitePageFactory {
private static Map<String, WebsitePage> pages = new HashMap<>();
public static WebsitePage getPage(String key) {
WebsitePage page = pages.get(key);
if (page == null) {
String sharedNavbar = "<Navbar>";
String sharedFooter = "<Footer>";
page = new ConcretePage(sharedNavbar, sharedFooter);
pages.put(key, page);
}
return page;
}
}
public class Main {
public static void main(String[] args) {
WebsitePage homePage = WebsitePageFactory.getPage("home");
WebsitePage aboutPage = WebsitePageFactory.getPage("about");
homePage.render("Home", "Welcome to our website!");
aboutPage.render("About", "Learn more about us!");
}
}
优点:
减少内存占用:通过共享相似对象的内部状态,减少了对象的数量,从而减少了内存的占用。
提高性能:由于减少了对象的数量,系统的性能得到了提升。
简化对象管理:享元模式将共享对象的创建和管理集中在工厂类中,简化了系统的设计和维护。
缺点:
增加了系统的复杂性:享元模式需要额外的工厂类来管理共享对象,增加了系统的复杂性。
对象状态共享可能引发线程安全问题:如果多个线程同时访问共享对象并修改其外部状态,可能会引发线程安全问题。
使用场景:
当一个系统中存在大量相似对象,且这些对象的大部分状态可以共享时,可以考虑使用享元模式来减少对象的数量。
当需要缓存对象以提高系统性能时,享元模式可以用于共享缓存对象。
当对象的状态大部分可以抽离为内部状态,而少量状态可以作为外部状态时,可以考虑使用享元模式。
2.13 行为型-策略模式
行为型-策略模式是一种软件设计模式,它允许在运行时选择算法的行为,并将其与主要的业务逻辑分离。该模式通过定义一组可互换的算法,并将每个算法封装在独立的类中,使得算法可以相互替换,而不影响客户端的使用。
首先,我们定义一个策略接口(Strategy):
public interface Strategy {
void execute();
}
然后,我们创建不同的具体策略类来实现策略接口:
public class ConcreteStrategyA implements Strategy {
@Override
public void execute() {
System.out.println("执行策略A");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void execute() {
System.out.println("执行策略B");
}
}
public class ConcreteStrategyC implements Strategy {
@Override
public void execute() {
System.out.println("执行策略C");
}
}
接下来,我们创建一个上下文类(Context),它将根据需要选择并使用不同的策略:
public class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
最后,我们可以在客户端代码中使用策略模式:
public class Client {
public static void main(String[] args) {
Context context = new Context();
Strategy strategyA = new ConcreteStrategyA();
context.setStrategy(strategyA);
context.executeStrategy(); // 输出:执行策略A
Strategy strategyB = new ConcreteStrategyB();
context.setStrategy(strategyB);
context.executeStrategy(); // 输出:执行策略B
Strategy strategyC = new ConcreteStrategyC();
context.setStrategy(strategyC);
context.executeStrategy(); // 输出:执行策略C
}
}
优点:
策略模式实现了算法的封装和切换,使得算法的变化不会影响到使用算法的客户端代码。
可以通过新增策略类来扩展系统,无需修改原有代码,符合开闭原则。
策略模式可以减少大量的条件语句,提高代码的可读性和可维护性。
缺点:
策略模式增加了系统中类的数量,增加了代码文件的体积和复杂性。
如果策略类过多或策略类比较庞大复杂,会导致系统结构变得复杂。
使用场景:
当一个系统需要动态地在多个算法中选择一个时,可以考虑使用策略模式。
当一个对象有多种行为,且这些行为可以在运行时根据需要切换时,可以使用策略模式。
当需要对算法进行扩展,增加新的算法时,可以通过添加新的策略类来实现扩展。
2.14 行为型-模板方法
行为型设计模式中的模板方法模式(Template Method)定义了一个算法的骨架,将一些步骤延迟到子类中实现。这样可以在不改变算法结构的情况下,通过子类来重新定义某些步骤的具体实现
abstract class AbstractClass {
// 模板方法,定义算法的骨架
public final void templateMethod() {
step1();
step2();
step3();
}
// 抽象方法,延迟到子类中实现
protected abstract void step1();
// 抽象方法,延迟到子类中实现
protected abstract void step2();
// 具体方法,已经有默认实现,子类可以选择是否重写
protected void step3() {
System.out.println("AbstractClass: step3");
}
}
class ConcreteClass extends AbstractClass {
// 实现抽象方法 step1
protected void step1() {
System.out.println("ConcreteClass: step1");
}
// 实现抽象方法 step2
protected void step2() {
System.out.println("ConcreteClass: step2");
}
// 重写具体方法 step3
protected void step3() {
System.out.println("ConcreteClass: step3");
}
}
public class Main {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
这个示例中,抽象类 AbstractClass 定义了一个模板方法 templateMethod(),该方法定义了算法的骨架。步骤 step1() 和 step2() 是抽象方法,需要延迟到子类中具体实现。步骤 step3() 是一个具体方法,它已经有默认实现,但子类可以选择是否重写。
优点:
提供了一种框架或算法的通用结构,使得算法的具体实现可以在子类中灵活定制,增强了代码的扩展性和复用性。
将相同的代码逻辑集中在父类中,避免了代码重复。
缺点:
可能导致某些子类只能通过继承父类来实现,限制了子类的灵活性。
如果算法的骨架发生变化,可能需要修改所有相关的子类。
使用场景:
当有一个算法需要多个步骤,并且每个步骤的具体实现可能有所不同时,可以考虑使用模板方法模式。
当需要定义一个算法的骨架,但具体的实现可以在子类中灵活定制时,模板方法模式也很有用。
模板方法模式适用于许多实际场景,例如软件框架中的钩子方法、流程控制、生命周期回调等。它在框架和库的设计中广泛应用,比如Java的Servlet生命周期、JUnit测试框架等。
2.15 行为型-责任链模式
行为型-责任链模式是一种软件设计模式,它允许多个对象以链式方式处理请求,每个对象都有机会处理请求或将其传递给链中的下一个对象。该模式解耦了发送者和接收者之间的关系,使得多个对象都有机会处理请求,增强了系统的灵活性和可扩展性。
// 定义抽象处理器类
abstract class Handler {
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(int request);
}
// 具体处理器类1
class ConcreteHandler1 extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 0 && request < 10) {
System.out.println("ConcreteHandler1 处理请求 " + request);
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
// 具体处理器类2
class ConcreteHandler2 extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 10 && request < 20) {
System.out.println("ConcreteHandler2 处理请求 " + request);
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
// 具体处理器类3
class ConcreteHandler3 extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 20 && request < 30) {
System.out.println("ConcreteHandler3 处理请求 " + request);
} else if (successor != null) {
successor.handleRequest(request);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();
handler1.setSuccessor(handler2);
handler2.setSuccessor(handler3);
int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };
for (int request : requests) {
handler1.handleRequest(request);
}
}
}
优点:
解耦发送者和接收者,每个处理器只需关注自己的责任,易于扩展和维护。
灵活性高,可以根据需求动态地组织处理器链,改变处理请求的顺序和条件。
可以在运行时决定请求的处理方式,增加系统的可配置性。
缺点:
由于请求可能在链中传递,处理请求的效率可能受到影响。
如果链过长或处理不当,可能会导致请求无法被正确处理或处理器无法被正确触发。
使用场景:
当需要将请求的发送者和接收者解耦,并希望多个对象都有机会处理请求时,可以使用责任链模式。
当需要动态地组织和配置处理器链以处理不同类型的请求时,可以使用责任链模式。
当系统中的处理逻辑具有层次结构,且希望灵活地调整请求的处理顺序时,可以使用责任链模式
2.16 行为型-观察者模式
行为型-观察者模式(Behavioral Observer Pattern)是一种软件设计模式,它建立了一种对象间的一对多依赖关系,当一个对象状态改变时,它的所有依赖者(观察者)都会收到通知并自动更新。这种模式促使了对象间的松耦合,使得它们可以独立地交互和通信。
观察者模式的主要参与者包括:
Subject(主题):维护一份观察者列表,提供方法用于添加、删除和通知观察者的状态变化。
Observer(观察者):定义了一个接口,用于接收主题状态的更新通知。
ConcreteSubject(具体主题):维护自身状态,并在状态变化时通知观察者。
ConcreteObserver(具体观察者):实现观察者接口,定义了在接收到更新通知时的具体行为。
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
private String message;
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(message);
}
}
public void setMessage(String message) {
this.message = message;
notifyObservers();
}
}
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
public class ObserverPatternExample {
public static void main(String[] args) {
Subject subject = new Subject();
ConcreteObserver observer1 = new ConcreteObserver("Observer 1");
ConcreteObserver observer2 = new ConcreteObserver("Observer 2");
subject.attach(observer1);
subject.attach(observer2);
subject.setMessage("Hello World!");
subject.detach(observer2);
subject.setMessage("New Message");
}
}
优点:
观察者模式实现了对象间的松耦合,主题和观察者之间的依赖关系被解耦,使得它们可以独立地变化。
支持广播通信机制,主题状态变化时可以通知多个观察者。
可以动态地添加和移除观察者。
缺点:
如果观察者较多或观察者之间有复杂的依赖关系,可能会导致性能问题。
观察者可能接收到不必要的通知,需要谨慎设计。
使用场景:
当一个对象的状态变化需要通知其他多个对象时,可以考虑使用观察者模式。例如,电子商务网站上的库存管理系统可以通知多个观察者(如销售部门、仓库管理部门等)商品库存的变化。
当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时,使用观察者模式可以将这两者封装在独立的对象中,使它们可以独立地进行扩展和重用。
当一个对象的改变需要同时改变其他对象,但又不希望对象之间紧密耦合时,观察者模式提供了一种灵活的解决方案。
2.17 行为型-访问者模式
行为型-访问者模式(Visitor Pattern)是一种常用的设计模式,用于将数据结构与对数据的操作分离。它允许在不改变数据结构的情况下定义新的操作。该模式通过将操作封装在称为"访问者"的对象中,使得可以在不修改数据结构的情况下增加新的操作。
下面是一个示例的Java代码来说明访问者模式的概念:
首先,定义一个元素接口(Element),它包含一个接受访问者对象的方法accept()
public interface Element {
void accept(Visitor visitor);
}
然后,实现两个具体的元素类(ConcreteElementA和ConcreteElementB):
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
// 具体元素A的操作
}
}
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationB() {
// 具体元素B的操作
}
}
接下来,定义一个访问者接口(Visitor),其中包含了对具体元素进行访问的方法
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
最后,实现一个具体的访问者类(ConcreteVisitor),它实现了访问者接口,并定义了具体的访问操作:
public class ConcreteVisitor implements Visitor {
@Override
public void visit(ConcreteElementA element) {
element.operationA();
// 访问具体元素A时的操作
}
@Override
public void visit(ConcreteElementB element) {
element.operationB();
// 访问具体元素B时的操作
}
}
这样,通过访问者模式,可以将对元素的操作(如operationA()和operationB())与元素的结构分离开来,实现了解耦。
优点:
新增具体操作时,无需修改元素类的结构,只需新增访问者类。
可以对元素进行多种不同的操作,而无需修改元素类。
访问者模式符合开闭原则,对于新增的元素类和操作,无需修改现有代码。
缺点:
增加新的元素类时,需要修改所有的访问者类,可能引起代码的维护困难。
访问者模式增加了系统的复杂性,理解和设计可能会更加困难。
使用场景:
当存在一个数据结构,且需要对该结构中的元素进行多种不同的操作时,可以考虑使用访问者模式。
当新增操作的频率较高,但数据结构的变动频率较低时,访问者模式可以减少代码修改的工作量。
当对数据结构的访问操作需要变化时,可以通过新增访问者类来实现变化,而无需修改现有的元素类。
2.18 行为型-中介者模式
行为型中介者模式是一种软件设计模式,旨在减少多个对象之间的直接通信,并通过引入中介者对象来促进对象之间的间接交互。该模式通过将对象之间的复杂关系转移到中介者上,从而简化了对象之间的交互,同时降低了对象之间的耦合性。
// 定义中介者接口
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
// 实现具体的中介者
class ConcreteMediator implements Mediator {
private Colleague colleague1;
private Colleague colleague2;
public void setColleague1(Colleague colleague1) {
this.colleague1 = colleague1;
}
public void setColleague2(Colleague colleague2) {
this.colleague2 = colleague2;
}
public void sendMessage(String message, Colleague colleague) {
if (colleague == colleague1) {
colleague2.receiveMessage(message);
} else if (colleague == colleague2) {
colleague1.receiveMessage(message);
}
}
}
// 定义抽象同事类
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public abstract void send(String message);
public abstract void receiveMessage(String message);
}
// 实现具体的同事类
class ConcreteColleague1 extends Colleague {
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}
public void send(String message) {
mediator.sendMessage(message, this);
}
public void receiveMessage(String message) {
System.out.println("ConcreteColleague1 received: " + message);
}
}
class ConcreteColleague2 extends Colleague {
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}
public void send(String message) {
mediator.sendMessage(message, this);
}
public void receiveMessage(String message) {
System.out.println("ConcreteColleague2 received: " + message);
}
}
// 使用中介者模式
public class Main {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.send("Hello from Colleague1");
colleague2.send("Hi from Colleague2");
}
}
中介者模式的优点包括:
减少了对象之间的直接通信,将复杂的交互关系转移到中介者上,简化了对象之间的交互。
降低了对象之间的耦合性,使对象之间的关系更加灵活和可扩展。
可以集中管理和控制相关对象之间的交互逻辑,提高了代码的可维护性和可读性。
中介者模式的缺点包括:
中介者对象会变得复杂,因为它需要处理和维护多个对象之间的交互关系。
过度使用中介者模式可能会导致中介者对象过于庞大,承担过多的责任,违反单一职责原则。
中介者模式适用于以下场景:
当多个对象之间存在复杂的交互关系,导致每个对象之间都需要知道其他对象的详细信息时,可以考虑使用中介者模式来简化交互。
当一组对象之间的交互逻辑分散在不同的类中,难以维护和理解时,可以引入中介者模式来集中管理和控制交互逻辑。
当想要降低对象之间的耦合性,使得对象之间的关系更加灵活和可扩展时,中介者模式可以提供一种解决方案
2.19 行为型-命令模式
行为型-命令模式是一种设计模式,用于将请求封装为一个对象,从而使发送者和接收者解耦。该模式允许请求的发送者无需知道请求的接收者是谁以及如何执行请求。它通过引入命令对象,将请求的发送者和接收者解耦,从而提高代码的灵活性和可扩展性。
// 定义命令接口
interface Command {
void execute();
}
// 具体的命令类
class ConcreteCommand implements Command {
private Receiver receiver;
ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
// 命令的接收者
class Receiver {
void action() {
System.out.println("执行命令!");
}
}
// 命令的发送者/调用者
class Invoker {
private Command command;
void setCommand(Command command) {
this.command = command;
}
void executeCommand() {
command.execute();
}
}
// 使用示例
public class CommandPatternExample {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand();
}
}
上述示例中,命令模式的核心是Command接口和具体的命令类ConcreteCommand。Command接口定义了执行命令的方法execute(),而ConcreteCommand实现了Command接口并持有一个命令的接收者Receiver对象。Receiver类定义了真正执行命令的方法action()。Invoker类用于设置具体的命令,并通过调用executeCommand()方法来执行命令。
命令模式的优点包括:
解耦发送者和接收者,使得代码更灵活和可扩展。
可以实现命令的撤销、重做等功能。
支持组合命令,即多个命令可以组合成一个更复杂的命令。
命令模式的缺点包括:
可能会导致类爆炸,即命令类的数量随着命令的增加而增加。
需要额外的类和接口来实现命令的封装,增加了代码量和复杂性。
命令模式适用于以下场景:
需要将请求发送者和请求接收者解耦的情况。
需要支持命令的撤销、重做等功能。
需要支持组合命令,以便实现更复杂的操作。
需要在不同的时间指定、排队和执行请求的情况下。
2.20 行为型-状态模式
行为型-状态模式(Behavioral-State Pattern)是一种软件设计模式,它允许对象在内部状态改变时改变其行为。状态模式将对象的行为封装在不同的状态类中,使得对象在不同状态下可以改变其行为而不改变其类。这种模式属于行为型模式,通过将状态的切换封装到状态类中,实现了状态与行为之间的解耦。
// 状态接口
interface State {
void handleState();
}
// 具体状态类1
class ConcreteState1 implements State {
public void handleState() {
System.out.println("处理状态1的行为");
}
}
// 具体状态类2
class ConcreteState2 implements State {
public void handleState() {
System.out.println("处理状态2的行为");
}
}
// 上下文类
class Context {
private State currentState;
public void setCurrentState(State state) {
this.currentState = state;
}
public void executeBehavior() {
currentState.handleState();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建状态对象
State state1 = new ConcreteState1();
State state2 = new ConcreteState2();
// 创建上下文对象
Context context = new Context();
// 设置初始状态
context.setCurrentState(state1);
// 执行行为
context.executeBehavior();
// 切换状态
context.setCurrentState(state2);
// 执行行为
context.executeBehavior();
}
}
状态模式的优点:
将状态的相关行为封装在各自的状态类中,使得代码更加清晰和易于维护。
状态模式符合开闭原则,当需要添加新的状态时,只需编写新的状态类,无需修改现有代码。
通过将状态转换逻辑移到上下文类外部,使得状态转换独立于具体状态类,提高了代码的灵活性和可扩展性。
状态模式的缺点:
如果状态较多或状态之间的转换逻辑较为复杂,会导致状态类的增加和复杂化,增加了系统的复杂性。
状态模式需要引入额外的类,增加了代码量。
状态模式适用的场景:
当一个对象的行为取决于其内部状态,并且在运行时可以动态地改变行为时,可以考虑使用状态模式。
当对象有多个状态,且不同状态下的行为差异较大时,可以使用状态模式将不同状态的行为进行封装和管理。
当需要避免使用过多的条件语句来判断对象的不同行为时,状态模式可以提供更加清晰和可扩展的解决方案
2.21 行为型-备忘录模式
行为型设计模式中的备忘录模式(Memento Pattern)用于捕获和存储一个对象的内部状态,以便在需要时可以将其恢复到先前的状态。它提供了一种方法,可以在不破坏封装性的情况下,捕获和恢复对象的内部状态。
概念:
备忘录模式涉及三个主要角色:发起人(Originator)、备忘录(Memento)和负责人(Caretaker)。发起人是拥有内部状态的对象,备忘录是用于存储发起人状态的对象,而负责人则负责保存和恢复发起人的状态。
示例代码(Java):
下面是一个简单的示例代码,演示了如何使用备忘录模式来保存和恢复发起人对象的状态
// 发起人类
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void restoreStateFromMemento(Memento memento) {
state = memento.getState();
}
}
// 备忘录类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 负责人类
class Caretaker {
private Memento memento;
public void setMemento(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
}
// 示例代码的使用
public class Main {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("State 1");
originator.setState("State 2");
caretaker.setMemento(originator.saveStateToMemento());
originator.setState("State 3");
System.out.println("Current state: " + originator.getState());
originator.restoreStateFromMemento(caretaker.getMemento());
System.out.println("Restored state: " + originator.getState());
}
}
优点:
发起人对象的封装性得到保护,其内部状态对其他对象是不可见的。
备忘录模式使得可以轻松保存和恢复发起人对象的状态,提供了一种可靠的回退机制。
支持多次撤销操作。
缺点:
如果发起人对象的状态过多或状态变化频繁,会占用较多的内存。
在恢复状态时,如果发起人对象的状态过多,可能会导致性能问题。
使用场景:
备忘录模式适用于以下情况:
需要保存和恢复对象状态的场景。
需要提供撤销操作的场景。
需要实现事务回滚的场景。
2.22 行为型-迭代器模式
行为型-迭代器模式是一种软件设计模式,用于提供一种访问聚合对象中元素的方法,而无需暴露其内部表示。迭代器模式通过提供一个统一的接口来遍历聚合对象的元素,客户端代码可以使用相同的方式遍历不同类型的聚合对象,从而提高代码的可重用性和灵活性
// 定义聚合接口
interface Aggregate {
Iterator createIterator();
}
// 实现具体的聚合类
class ConcreteAggregate implements Aggregate {
private String[] items;
public ConcreteAggregate() {
items = new String[5];
items[0] = "Item 1";
items[1] = "Item 2";
items[2] = "Item 3";
items[3] = "Item 4";
items[4] = "Item 5";
}
public Iterator createIterator() {
return new ConcreteIterator(this);
}
public int count() {
return items.length;
}
public String getItem(int index) {
return items[index];
}
}
// 定义迭代器接口
interface Iterator {
boolean hasNext();
Object next();
}
// 实现具体的迭代器类
class ConcreteIterator implements Iterator {
private ConcreteAggregate aggregate;
private int currentIndex;
public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
currentIndex = 0;
}
public boolean hasNext() {
return currentIndex < aggregate.count();
}
public Object next() {
return aggregate.getItem(currentIndex++);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ConcreteAggregate aggregate = new ConcreteAggregate();
Iterator iterator = aggregate.createIterator();
while (iterator.hasNext()) {
Object item = iterator.next();
System.out.println(item);
}
}
}
迭代器模式的优点包括:
将聚合对象和遍历算法解耦,使得代码更加模块化和可维护。
提供了一种统一的遍历接口,使得客户端代码可以透明地处理不同类型的聚合对象。
支持多种遍历方式,如顺序遍历、逆序遍历等。
然而,迭代器模式也有一些缺点:
增加了额外的类和接口,增加了代码复杂性。
在某些情况下,使用迭代器模式可能会降低性能,特别是对于底层数据结构复杂的聚合对象。
迭代器模式适用于以下场景:
当需要遍历一个聚合对象的元素,并且不想暴露其内部结构时,可以使用迭代器模式。
当希望提供多种遍历方式,并且希望客户端代码能够以统一的方式处理不同类型的聚合对象时,可以使用迭代器模式。
当需要实现自定义的迭代器,以支持特定的遍历逻辑时,可以使用迭代器模式。
2.23 行为型-解释器模式
行为型-解释器模式(Interpreter Pattern)是一种设计模式,用于解释编程语言的语法或表达式,并执行相应的操作。它属于行为型模式的一种,可以用于实现简单的编译器和解释器。
在解释器模式中,通过定义一个语言的语法表示,并解析这个语法表示来执行特定的操作。它包含以下主要组件:
抽象表达式(Abstract Expression):定义了一个抽象的解释操作,所有具体表达式都需要实现这个接口。
终结符表达式(Terminal Expression):表示语言中的一个终结符或一个变量,并通过实现抽象表达式接口来进行解释操作。
非终结符表达式(Non-terminal Expression):表示语言中的一个非终结符,并通过递归调用自身来解释表达式。
上下文(Context):包含解释器需要的全局信息或状态。
// 抽象表达式接口
interface Expression {
int interpret(Context context);
}
// 终结符表达式
class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
public int interpret(Context context) {
return number;
}
}
// 非终结符表达式 - 加法
class AddExpression implements Expression {
private Expression leftExpression;
private Expression rightExpression;
public AddExpression(Expression leftExpression, Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
public int interpret(Context context) {
return leftExpression.interpret(context) + rightExpression.interpret(context);
}
}
// 上下文类
class Context {
private String input;
private int output;
public Context(String input) {
this.input = input;
}
public String getInput() {
return input;
}
public void setOutput(int output) {
this.output = output;
}
public int getOutput() {
return output;
}
}
// 使用示例
public class InterpreterExample {
public static void main(String[] args) {
// 创建上下文对象
Context context = new Context("1+2+3");
// 解析表达式
Expression expression = new AddExpression(
new AddExpression(
new NumberExpression(1),
new NumberExpression(2)
),
new NumberExpression(3)
);
// 执行解释操作
int result = expression.interpret(context);
context.setOutput(result);
System.out.println(context.getInput() + " = " + context.getOutput());
}
}
解释器模式的优点:
灵活性:它可以通过定义新的表达式来扩展语言或修改解释器的行为。
易于实现语法:它使得语法规则的表示和解释变得简单明了。
易于改变解释器:可以通过改变解释器的组成来改变解释执行的算法。
解释器模式的缺点:
可能导致类的数量增加:对于复杂的语法规则,可能需要大量的类来表示各种组合,导致类的数量增加。
执行效率较低:每个表达式的解释都需要进行递归调用,可能会导致性能下降。
解释器模式的使用场景:
当有一个语言需要解释执行,并且可以将该语言的语法规则表示为一个抽象语法树时,解释器模式是一个很好的选择。
当需要实现简单的脚本语言或编程语言的解释器时,可以考虑使用解释器模式。
当需要实现复杂的规则或工作流程引擎时,解释器模式也可以用于解释和执行规则或流程定义。