设计模式是软件开发中解决一类问题的通用方法。
使用设计模式能让你写出更优雅、可维护的代码,也正因如此,很多框架源码都用到了设计模式,你不学很难看懂。
- 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程
- 单例模式
- 工厂方法模式
- 抽象工厂
- 建造者模式
- 原型模式
- 结构型模式:把类或对象结合在一起形成一个更大的结构
- 适配器模式
- 组合模式
- 装饰器模式
- 代理模式
- 享元模式
- 外观模式
- 桥接模式
- 行为型模式:类和对象如何交互,及划分责任和算法
- 迭代器模式
- 模板方法模式
- 策略模式
- 命令模式
- 状态模式
- 责任链模式
- 备忘录模式
- 观察者模式
- 访问者模式
- 中介者模式
- 解释器模式
创建型模式
创建型模式(Creational Pattern):对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。
(5 种)单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
单例模式
懒汉式
package com.mqb.design.signton.lazy;
/**
* @author qingbomy
* @date 2023/2/20 19:51
*
*/
public class LazySigntonTest {
public static void main(String[] args) {
//单线程
LazySignton instance = LazySignton.getInstance();
LazySignton instance1 = LazySignton.getInstance();
System.out.println(instance1==instance);
}
}
class LazySignton {
private static LazySignton lazySignton;
private LazySignton() {
}
public static LazySignton getInstance() {
if (lazySignton == null) {
lazySignton = new LazySignton();
}
return lazySignton;
}
}
上面这种方法在多线程的情况下会出现多个实例,解决方法是在getInstance
方法上添加synchronized
public synchronized static LazySignton getInstance() {
if (lazySignton == null) {
lazySignton = new LazySignton();
}
return lazySignton;
}
但是加锁会造成性能问题,优化点,当 lazySignton 为空的时候加锁,不为空的时候直接取用
public static LazySignton getInstance() {
if (lazySignton == null) {
//防止多线程同时对该类加锁
synchronized(LazySignton.class){
//双重检查
if (lazySignton == null){
lazySignton = new LazySignton();
}
}
}
return lazySignton;
}
在字节码成面看,上面的方法同样会有问题,可能会出现一个空指针的问题
当 t1 线程进行 分配空间->引用复制但是还没有进行初始化的时候,
t2 线程发现 instance 有值了,直接返回 instance,这时候再去调用就可能会出现 NPE 的问题
解决方法(使用 volatile,防止指令重排
(在字节码成面,就会按照:分配空间->初始化->引用赋值的顺序执行),volatile 的另外两点:保证多线程间的可见性。不保证原子性)
class LazySignton {
private volatile static LazySignton lazySignton;
private LazySignton() {
}
public static LazySignton getInstance() {
if (lazySignton == null) {
synchronized(LazySignton.class){
if (lazySignton == null){
lazySignton = new LazySignton();
}
}
}
return lazySignton;
}
}
饿汉式
class HungrySignton {
private static HungrySignton instance = new HungrySignton();
private HungrySignton() {
}
public static HungrySignton getInstance() {
return instance;
}
}
基于 JVM 的类加载机制保证线程的安全
类加载(真正时候这个类的时候触发)的步骤,
- 将二进制文件加载内存并生成 class 数据结构
- 连接: 验证、准备(给类的静态成员变量赋默认值,0,null...)、解析
- 初始化:给类的静态变量赋值
静态内部类
class StaticInnerClassSign {
private static class StaticInner {
private static StaticInnerClassSign instance = new StaticInnerClassSign();
}
private StaticInnerClassSign(){}
public StaticInnerClassSign getInstance(){
return StaticInner.instance;
}
}
也是基于 JVM 的类加载机制实现
注意:通过反射可以获取多个实例,可以在静态内部类处理
修改静态内部类
class StaticInnerClassSign {
private static class StaticInner {
private static StaticInnerClassSign instance = new StaticInnerClassSign();
}
private StaticInnerClassSign(){
if (StaticInner.instance!=null){
throw new RuntimeException("单例模式实例化失败,存在多个实例");
}
}
public StaticInnerClassSign getInstance(){
return StaticInner.instance;
}
}
利用反射获取实例
@SneakyThrows
public static void main(String[] args) {
Class<StaticInnerClassSign> staticInnerClassSignClass = StaticInnerClassSign.class;
Constructor<StaticInnerClassSign> declaredConstructor = staticInnerClassSignClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
StaticInnerClassSign staticInnerClassSign = declaredConstructor.newInstance();
StaticInnerClassSign instance = StaticInnerClassSign.getInstance();
System.out.println(staticInnerClassSign==instance);
}
工厂模式
简单工厂模式
package com.mqb.design.factory.simpleFactory;
public abstract class Product{
public abstract void Show();
}
实现类
//具体产品类A
class ProductA extends Product{
@Override
public void Show() {
System.out.println("生产出了产品A");
}
}
//具体产品类B
class ProductB extends Product{
@Override
public void Show() {
System.out.println("生产出了产品B");
}
}
//具体产品类C
class ProductC extends Product{
@Override
public void Show() {
System.out.println("生产出了产品C");
}
}
class Factory {
public Product manufacture(String productName){
//工厂类里用switch语句控制生产哪种商品;
//使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
return switch (productName) {
case "A" -> new ProductA();
case "B" -> new ProductB();
case "C" -> new ProductC();
default -> null;
};
}
}
使用
package com.mqb.design.factory.simpleFactory;
/**
* 简单工厂模式
*
* @author qingbomy
* @date 2023/2/25 13:51
*
*/
public class SimpleFactory {
public static void main(String[] args) {
Factory mFactory = new Factory();
//客户要产品A
try {
//调用工厂类 的静态方法 & 传入不同参数从而创建产品实例
mFactory.manufacture("A").Show();
} catch (NullPointerException e) {
System.out.println("没有这一类产品");
}
//客户要产品B
try {
mFactory.manufacture("B").Show();
} catch (NullPointerException e) {
System.out.println("没有这一类产品");
}
//客户要产品C
try {
mFactory.manufacture("C").Show();
} catch (NullPointerException e) {
System.out.println("没有这一类产品");
}
//客户要产品D
try {
mFactory.manufacture("D").Show();
} catch (NullPointerException e) {
System.out.println("没有这一类产品");
}
}
}
工厂方法模式
当一个工厂需要新生产另一种产品时可以考虑使用工厂模式
工厂的抽象类
/**
* 抽象工厂类,定义具体工厂的公共接口
*/
public interface Factory{
/**
* 制造
* @return
*/
public abstract Product manufacture();
}
需要生产的产品的接口
package com.mqb.design.factory.factoryMethod;
public interface Product{
/**
* 定义具体产品的公共接口;
*/
public void show();
}
产品
package com.mqb.design.factory.factoryMethod;
//具体产品A类
public class ProductA implements Product{
@Override
public void show() {
System.out.println("生产出了产品A");
}
}
//具体产品B类
class ProductB implements Product{
@Override
public void show() {
System.out.println("生产出了产品B");
}
}
工厂的两个实现类,分别生产 A 和 B 产品
package com.mqb.design.factory.factoryMethod;
//工厂A类 - 生产A类产品
public class FactoryA implements Factory{
@Override
public Product manufacture() {
return new ProductA();
}
}
class FactoryB implements Factory{
@Override
public Product manufacture() {
return new ProductB();
}
}
使用
public static void main(String[] args){
//客户要产品A
Factory mFactoryA = new FactoryA();
mFactoryA.manufacture().show();
//客户要产品B
Factory mFactoryB = new FactoryB();
mFactoryB.manufacture().show();
}
抽象工厂模式
工厂接口
package com.mqb.design.factory.abstractFactory;
/**
* @author qingbomy
* @date 2023/2/25 21:28
*
*/
public interface Factory {
/**
* 生产手机
* @return
*/
Phone productPhone();
/**
* 生产耳机
* @return
*/
Earphone productEarphone();
}
手机接口
package com.mqb.design.factory.abstractFactory;
/**
* @author qingbomy
* @date 2023/2/25
*
*/
public interface Phone {
/**
* 开机
*/
void start();
}
耳机接口
package com.mqb.design.factory.abstractFactory;
/**
* 耳机
* @author qingbomy
* @date 2023/2/25
*
*/
public interface Earphone {
/**
* 耳机品牌
*/
void show();
}
两个工厂,分别是华为工厂和苹果工厂,分别生产各自的手机和耳机
苹果
package com.mqb.design.factory.abstractFactory.apple;
import com.mqb.design.factory.abstractFactory.Factory;
import com.mqb.design.factory.abstractFactory.Earphone;
import com.mqb.design.factory.abstractFactory.Phone;
/**
* @author qingbomy
* @date 2023/2/25 21:35
*
*/
public class AppleFactory implements Factory {
@Override
public Phone productPhone() {
return new IPhone();
}
@Override
public Earphone productEarphone() {
return new AirPods();
}
}
package com.mqb.design.factory.abstractFactory.apple;
import com.mqb.design.factory.abstractFactory.Phone;
/**
* @author qingbomy
* @date 2023/2/25 21:36
*
*/
public class IPhone implements Phone {
@Override
public void start() {
System.out.println("iPhone 14 phone");
}
}
package com.mqb.design.factory.abstractFactory.apple;
import com.mqb.design.factory.abstractFactory.Earphone;
/**
* @author qingbomy
* @date 2023/2/25 21:36
*
*/
public class AirPods implements Earphone {
@Override
public void show() {
System.out.println("AirPods Max");
}
}
华为
package com.mqb.design.factory.abstractFactory.huawei;
import com.mqb.design.factory.abstractFactory.Factory;
import com.mqb.design.factory.abstractFactory.Earphone;
import com.mqb.design.factory.abstractFactory.Phone;
/**
* @author qingbomy
* @date 2023/2/25 21:32
*
*/
public class HuaweiFactory implements Factory {
@Override
public Phone productPhone() {
return new HuaweiPhone();
}
@Override
public Earphone productEarphone() {
return new HuaweiEarphone();
}
}
package com.mqb.design.factory.abstractFactory.huawei;
import com.mqb.design.factory.abstractFactory.Earphone;
/**
* @author qingbomy
* @date 2023/2/25 21:34
*
*/
public class HuaweiEarphone implements Earphone {
@Override
public void show() {
System.out.println("HUAWEI FreeBuds Pro 2");
}
}
package com.mqb.design.factory.abstractFactory.huawei;
import com.mqb.design.factory.abstractFactory.Phone;
/**
* @author qingbomy
* @date 2023/2/25 21:33
*
*/
public class HuaweiPhone implements Phone {
@Override
public void start() {
System.out.println("HUAWEI Mate 50 Pro");
}
}
测试
public static void main(String[] args) {
Factory huaweiFactory = new HuaweiFactory();
Earphone earphone = huaweiFactory.productEarphone();
Phone phone = huaweiFactory.productPhone();
phone.start();
earphone.show();
System.out.println("========================");
Factory appleFactory = new AppleFactory();
Earphone appleEarphone = appleFactory.productEarphone();
Phone applePhone = appleFactory.productPhone();
appleEarphone.show();
applePhone.start();
}
后续如果想增加产品族,或者是拓展产品等级,参考如下方式
增加产品族
需要修改顶层的工厂,如果苹果和华为都需要生产平板,那么需要在 Factory 接口增加生产平板的接口,并且还要有一个平板的产品接口实现类
public interface Pad {
/**
* 屏幕大小
*/
void size();
}
public interface Factory {
/**
* 生产手机
*
* @return
*/
Phone productPhone();
/**
* 生产耳机
*
* @return
*/
Earphone productEarphone();
default Pad productPad() {
return null;
}
}
拓展产品等级
小米也可以生产手机,耳机,平板,那么小米直接实现 Factory,Phone,Earphone,Pad 接口即可
建造者模式
public class User {
private final String firstName;
private final String lastName;
private final int age;
private final String phone;
private final String address;
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
private String firstName; // 必传参数
private String lastName; // 必传参数
private int age; // 可选参数
private String phone; // 可选参数
private String address; // 可选参数
public UserBuilder() {
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
原型模式
用于创建重复的对象,同时又能保证性能
原型模式是一种创建型设计模式,它允许通过复制(或“克隆”)现有对象来创建新对象,而无需依赖于显式的构造函数。在原型模式中,我们创建一个基础对象(称为原型),然后复制该对象以创建新对象。
使用原型模式创建新对象时,可以选择深拷贝或浅拷贝。深拷贝会创建一个新的对象,并将所有属性和子对象都复制到新对象中。而浅拷贝则只复制基本类型的属性,而不复制子对象。
在 Java 中,原型模式可以通过实现 Cloneable
接口和覆盖 clone()
方法来实现。Cloneable
接口是一个标记接口,它表明该类可以被复制。当调用 clone()
方法时,会创建一个新的对象并将原始对象的属性值复制到新对象中。
使用原型模式的优点包括:
- 可以避免重复创建对象,提高性能。
- 可以动态地添加或更改对象的属性,而无需修改代码。
- 可以隐藏创建新对象的细节。
但是,需要注意以下几点:
- 对象必须满足一定的条件才能进行复制,例如必须实现 Cloneable 接口。
- 深拷贝可能会导致性能问题,特别是对于包 含大量数据的对象。
- 在并发环境下,需要考虑多线程访问同一原型对象的情况。
定义抽象类 Shape,重写 equals 和 clone
package com.mqb.design.prototype;
import java.util.Objects;
public abstract class Shape implements Cloneable{
public int x;
public int y;
public String color;
public Shape() {
}
public Shape(Shape target) {
if (target != null) {
this.x = target.x;
this.y = target.y;
this.color = target.color;
}
}
@Override
public abstract Shape clone();
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Shape)) {
return false;
}
Shape shape2 = (Shape) object2;
return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color);
}
}
实现类 Circle,重写父类的 clone
public class Circle extends Shape {
public int radius;
public Circle() {
}
public Circle(Circle target) {
super(target);
if (target != null) {
this.radius = target.radius;
}
}
@Override
public Shape clone() {
return new Circle(this);
}
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Circle shape2) || !super.equals(object2)) {
return false;
}
return shape2.radius == radius;
}
}
实现类 Rectangle,重写父类的 clone
public class Rectangle extends Shape {
public int width;
public int height;
public Rectangle() {
}
public Rectangle(Rectangle target) {
super(target);
if (target != null) {
this.width = target.width;
this.height = target.height;
}
}
@Override
public Shape clone() {
return new Rectangle(this);
}
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Rectangle) || !super.equals(object2)) {
return false;
}
Rectangle shape2 = (Rectangle) object2;
return shape2.width == width && shape2.height == height;
}
}
测试
package com.mqb.design.prototype;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
List<Shape> shapesCopy = new ArrayList<>();
Circle circle = new Circle();
circle.x = 10;
circle.y = 20;
circle.radius = 15;
circle.color = "red";
shapes.add(circle);
Circle anotherCircle = (Circle) circle.clone();
shapes.add(anotherCircle);
Rectangle rectangle = new Rectangle();
rectangle.width = 10;
rectangle.height = 20;
rectangle.color = "blue";
shapes.add(rectangle);
cloneAndCompare(shapes, shapesCopy);
}
/**
* 克隆并比较
* @param shapes
* @param shapesCopy
*/
private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) {
for (Shape shape : shapes) {
shapesCopy.add(shape.clone());
}
for (int i = 0; i < shapes.size(); i++) {
if (shapes.get(i) != shapesCopy.get(i)) {
System.out.println(i + "两个对象的地址是不相同的");
if (shapes.get(i).equals(shapesCopy.get(i))) {
System.out.println(i + "两个对象的内容是相同的");
} else {
System.out.println(i + "两个对象的内容不是相同的");
}
} else {
System.out.println(i + "两个对象的地址相同");
}
}
}
}
输出结果
0 两个对象的地址是不相同的 0 两个对象的内容是相同的 1 两个对象的地址是不相同的 1 两个对象的内容是相同的 2 两个对象的地址是不相同的 2 两个对象的内容是相同的
说明使用原型模式克隆了一个内容相同地址不同的对象
结构性模式
结构型模式(Structural Pattern):关注于对象的组成以及对象之间的依赖关系,描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。
(7 种)适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式
适配器模式
假设系统有日志工厂类
//系统日志
public interface LogFactory {
void debug(String message);
}
第三方的接口与系统的接口不适配
//第三方接口
public interface Logger {
void d(int priority, String message, Object ... obj);
}
第三方接口的实现类
public class LoggerImp implements Logger {
@Override
public void d(int priority, String message, Object... obj) {
System.out.println(String.format("logger记录:%s",message));
}
}
如果系统要使用这个第三方的日志实现类,需要编写日志适配器来进行兼容
public class LogAdapter implements LogFactory {
private Logger logger;
public LogAdapter(Logger logger) {
this.logger = logger;
}
@Override
public void debug( String message) {
Objects.requireNonNull(logger);
logger.d(1, message);
}
}
测试
public class Demo {
public static void main(String[] args) {
LogFactory logFactory = new LogAdapter(new LoggerImp());
logFactory.debug("开始输出日志...");
}
}
logger 记录:开始输出日志...
装饰者模式
装饰器模式是一种结构型设计模式,它允许你在不修改现有对象的情况下向其添加新功能。该模式涉及创建一个装饰器类,在其中包含与要装饰的原始对象相同的方法,并在其中添加自定义行为。通过将原始对象传递给装饰器对象,可以动态地修改对象的行为。这样可以使代码更加灵活和可扩展,而无需对原始对象进行直接修改。
我们有一个 DataSource
接口,它被实现为一个简单的文件读取器。我们希望能够对该文件读取器进行加密和压缩等操作,而不改动原本的代码。
为了实现这一目标,我们首先创建了一个基础装饰器类 DataSourceDecorator
,它也实现了 DataSource
接口,并持有一个内部的 DataSource
类型的成员变量。然后,我们可以派生出一个具体的装饰器类,例如 EncryptionDecorator
和 CompressionDecorator
,它们分别添加了数据加密和数据压缩的功能
public interface DataSource {
void writeData(String data);
String readData();
}
文件读取器
package com.mqb.design.decorator;
import java.io.*;
/**
* 文件数据源,实现了DataSource,重写了读写方法
* @author qingbomy
*/
public class FileDataSource implements DataSource {
private String name;
public FileDataSource(String name) {
this.name = name;
}
@Override
public void writeData(String data) {
File file = new File(name);
try (OutputStream fos = new FileOutputStream(file)) {
fos.write(data.getBytes(), 0, data.length());
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
@Override
public String readData() {
char[] buffer = null;
File file = new File(name);
try (FileReader reader = new FileReader(file)) {
buffer = new char[(int) file.length()];
reader.read(buffer);
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
return new String(buffer);
}
}
基础装饰器类
public class DataSourceDecorator implements DataSource {
private DataSource wrappee;
DataSourceDecorator(DataSource source) {
this.wrappee = source;
}
@Override
public void writeData(String data) {
wrappee.writeData(data);
}
@Override
public String readData() {
return wrappee.readData();
}
}
具体的装饰器类(加解密)
/**
* 对数据源装饰器进行装饰(添加新功能:加密)
*/
public class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) {
super(source);
}
@Override
public void writeData(String data) {
super.writeData(encode(data));
}
@Override
public String readData() {
return decode(super.readData());
}
/**
* 加密
* @param data
* @return 密文
*/
private String encode(String data) {
byte[] result = data.getBytes();
for (int i = 0; i < result.length; i++) {
result[i] += (byte) 1;
}
return Base64.getEncoder().encodeToString(result);
}
/**
* 解密
* @param data
* @return 明文
*/
private String decode(String data) {
byte[] result = Base64.getDecoder().decode(data);
for (int i = 0; i < result.length; i++) {
result[i] -= (byte) 1;
}
return new String(result);
}
}
具体的装饰器类(解压缩)
package com.mqb.design.decorator;
/**
* 对数据源装饰器进行装饰(添加新功能:压缩)
*
* @author qingbomy
*/
public class CompressionDecorator extends DataSourceDecorator {
private int compLevel = 6;
public CompressionDecorator(DataSource source) {
super(source);
}
public int getCompressionLevel() {
return compLevel;
}
public void setCompressionLevel(int value) {
compLevel = value;
}
@Override
public void writeData(String data) {
super.writeData(compress(data));
}
@Override
public String readData() {
return decompress(super.readData());
}
private String compress(String stringData) {
byte[] data = stringData.getBytes();
ByteArrayOutputStream bout=null;
DeflaterOutputStream dos=null;
try {
bout = new ByteArrayOutputStream(512);
dos = new DeflaterOutputStream(bout, new Deflater(compLevel));
dos.write(data);
dos.close();
bout.close();
return Base64.getEncoder().encodeToString(bout.toByteArray());
} catch (IOException ex) {
return null;
} finally {
try {
assert bout != null;
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
assert dos != null;
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String decompress(String stringData) {
byte[] data = Base64.getDecoder().decode(stringData);
InputStream in = null;
InflaterInputStream iin = null;
ByteArrayOutputStream bout = null;
try {
in = new ByteArrayInputStream(data);
iin = new InflaterInputStream(in);
bout = new ByteArrayOutputStream(512);
int b;
while ((b = iin.read()) != -1) {
bout.write(b);
}
return bout.toString();
} catch (IOException ex) {
return null;
} finally {
assert bout != null;
try {
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
iin.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试
先对 salaryRecords 进行压缩,之后再对压缩后的数据进行加密,最后输出到 OutputDemo.txt 文件中
package com.mqb.design.decorator;
public class Demo {
public static void main(String[] args) {
String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
DataSource ds = new FileDataSource("out/OutputDemo.txt");
EncryptionDecorator encryptionDecorator = new EncryptionDecorator(ds);
DataSourceDecorator encoded = new CompressionDecorator(encryptionDecorator);
encoded.writeData(salaryRecords);
System.out.println("- 输入 ----------------");
System.out.println(salaryRecords);
System.out.println("- 加密 --------------");
System.out.println(ds.readData());
System.out.println("- 解密 --------------");
System.out.println(encoded.readData());
}
}
- 输入 ---------------- Name,Salary John Smith,100000 Steven Jobs,912000
- 加密 -------------- Zkt7e1Q5eU8yUm1Qe0ZsdHJ2VXp6dDBKVnhrUHtUe0sxRUYxQkJIdjVLTVZ0dVI5Q2IwOXFISmVUMU5rcENCQmdxRlByaD4+
- 解密 -------------- Name,Salary John Smith,100000 Steven Jobs,912000
代理模式
代理模式是一种结构型设计模式,可以为其他对象提供一种代理或占位符,以控制对该对象的访问。代理是在客户端和实际目标对象之间建立的一个中介,它扮演着传递请求、过滤请求和管理请求的角色。
代理模式有多种形式,包括远程代理、虚拟代理和保护代理等 。远程代理通过网络连接实现两个不同地址空间内的对象通信;虚拟代理可延迟加载大型图像等资源,以提高程序性能;保护代理则控制对目标对象的访问权限,只允许特定用户或对象进行访问。
代理模式的适用场景包括:需要远程连接的应用程序;需要控制对敏感资源的访问权限的应用程序;需要延迟加载资源的应用程序;需要在一个已有的对象基础上添加额外功能的应用程序等。
代理模式的优点包括:能够增强代码的可维护性;能够减少系统的耦合度;能够提高代码的复用率;能够实现更好的性能和可伸缩性。
代理模式的缺点包括:可能会降低程序的效率;可能会增加代码的复杂度;可能会增加程序的开发难度。
假设有微信可执行文件,为微信可执行文件创建快捷方式(代理)
public interface ExecutableFile {
void exec();
}
public class WechatExe implements ExecutableFile{
@Override
public void exec() {
System.out.println("执行微信.exe文件");
}
}
public class ExecutableFileProxy implements ExecutableFile {
private WechatExe wechatExe;
public ExecutableFileProxy(WechatExe wechatExe) {
this.wechatExe = wechatExe;
}
@Override
public void exec() {
if (wechatExe==null){
this.wechatExe = new WechatExe();
}
wechatExe.exec();
}
}
测试使用代理来执行.exe 文件
public class Demo {
public static void main(String[] args) {
ExecutableFile executableFile = new ExecutableFileProxy(new WechatExe());
executableFile.exec();
}
}
外观模式
外观模式就像是酒店的前台服务员,他们为客人提供一个方便的接口来处理住宿过程中的所有问题,比如登记入住、办理退房、预订旅游等等。客人不需要亲自到酒店各个部门去办理这些事情,而是通过前台服务员一站式解决。
在软件设计中,外观模式也是一样的道理。它将底层复杂组件的操作封装在一个统一的接口后面,对于使用者来说,只需要调用这个接口就可以完成所需的功能,而不必关心具体实现的细节。这样做的好处是提高了程序的可维护性和可扩展性,减少了代码的重复性和耦合度,更加方便开发和维护程序。
首先,我们创建一个外观类 ComputerFacade
,它提供了一个高层次的接口,将客户端和底层子系统隔离开来。在这个示例中,我们假设底层子系统由 CPU、Memory 和 HardDrive 三个组件构成,而客户端只需要调用 start()
方法就可以启动计算机,而不必关心每个组件的具体实现过程。
public class CPU {
public void freeze() {
System.out.println("暂停 CPU 的执行");
}
public void jump(int position) {
System.out.println("表示将指针跳转到指定位置");
}
public void execute() {
System.out.println("执行指令");
}
}
public class HardDrive {
public byte[] read(int lba, int size) {
System.out.printf("从%d位置开始,读取大小为%s的数据\n", lba, size);
return "hello".getBytes(StandardCharsets.UTF_8);
}
}
public class Memory {
public void load(int position, byte[] data) {
System.out.printf("从%d位置开始,读取的数据为%s\n", position, Arrays.toString(data));
}
public byte[] read(int position, int size) {
System.out.printf("从%d位置开始,读取大小为%d的数据\n", position, size);
return "hello".getBytes(StandardCharsets.UTF_8);
}
}
外观类
public class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
cpu.freeze();
memory.load(0, hardDrive.read(0, 100));
cpu.jump(0);
cpu.execute();
}
}
测试
public class Demo {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
computer.start();
}
}
暂停 CPU 的执行 从 0 位置开始,读取大小为 100 的数据 从 0 位置开始,读取的数据为[104, 101, 108, 108, 111] 表示将指针跳转到指定位置 执行指令
桥接模式
它的主要目的是将抽象和实现分离开来,从而实现松耦合的设计。它通过将抽象部分与实现部分分别设计,然后再通过一个桥接接口将它们连接起来,使得它们可以独立地变化,互不影响。
具体来说,桥接模式中包含两个重要组成部分:抽象部分和实现部分。抽象部分定义了客户端需要的接口和方法,而实现部分定义了具体的实现方式。这两部分之间通过一个桥接接口相互连接,从而使得客户端可以调用到正确的实现方式。
举例,假设我们需要实现一个电视机遥控器。我们可以将遥控器本身作为抽象部分,定义一些通用的方法和接口,如开关机、音量调整、频道切换等;而将具体的电视机品牌作为实现部分,例如海尔、TCL 等,每个品牌都有自己的实现方式。最后,我们通过一个桥接接口将遥控器与电视机品牌连接起来,从而实现电视机遥控器的功能。
桥接模式的优点在于它能够减少系统中各个部分之间的耦合度,使得系统更加灵活、易于扩展和维护。同时,它也能够提高代码的可读性和可维护性,使得系统更加稳定和可靠。
Demo 代码
假设我们有两种不同品牌的电视机,分别是海尔和 TCL,它们具体实现了电视机的开关、音量调整和频道切换等操作。
创建一个具体实现部分 TV
,它定义了电视机具体的操作方法。这里我们分别创建了 HaierTV
和 TCLTV
两个具体实现类,分别对应不同品牌的电视机。每个具体实现类都实现了 TV
接口中定义的方法。
public interface TV {
void on();
void off();
void turnChannel(int channel);
void turnUp();
void turnDown();
}
TCL 电视
public class TCLTV implements TV {
@Override
public void on() { System.out.println("TCL TV is on."); }
@Override
public void off() { System.out.println("TCL TV is off."); }
@Override
public void turnChannel(int channel) { System.out.println("TCL TV switched to channel " + channel); }
@Override
public void turnUp() { System.out.println("TCL TV volume turned up."); }
@Override
public void turnDown() { System.out.println("TCL TV volume turned down."); }
}
Haier 电视
public class HaierTV implements TV {
@Override
public void on() { System.out.println("Haier TV is on."); }
@Override
public void off() { System.out.println("Haier TV is off."); }
@Override
public void turnChannel(int channel) { System.out.println("Haier TV switched to channel " + channel); }
@Override
public void turnUp() { System.out.println("Haier TV volume turned up."); }
@Override
public void turnDown() { System.out.println("Haier TV volume turned down."); }
}
遥控器抽象类
public abstract class Remote {
protected TV tv;
public Remote(TV tv) {
this.tv = tv;
}
/**
* 电源开关
*/
public abstract void power();
/**
* 音量+
*/
public abstract void volumeUp();
/**
* 音量-
*/
public abstract void volumeDown();
/**
* 向上选择频道
*/
public abstract void channelUp();
/**
* 向下选择频道
*/
public abstract void channelDown();
/**
* 选择频道
* @param num
*/
public abstract void turnChannel(int num);
}
具体的遥控器
public class ConcreteRemote extends Remote {
public ConcreteRemote(TV tv) {
super(tv);
}
@Override
public void power() {
tv.on();
}
@Override
public void volumeUp() {
tv.turnUp();
}
@Override
public void volumeDown() {
tv.turnDown();
}
@Override
public void channelUp() {
int currentChannel = 1; // 假设当前频道是 1
tv.turnChannel(currentChannel + 1);
}
@Override
public void channelDown() {
int currentChannel = 1; // 假设当前频道是 1
tv.turnChannel(currentChannel - 1);
}
@Override
public void turnChannel(int num) {
tv.turnChannel(num);
}
}
测试
我们创建一个具体实现类的实例(例如 HaierTV
),然后将它与一个抽象部分(例如 Remote
)关联起来。这里的抽象部分充当桥接接口的作用,它将遥控器与具体的电视品牌连接起来。通过这种方式,我们就可以使用遥控器来控制不同品牌的电视机了。
public class Demo {
public static void main(String[] args) {
TV tv = new TCLTV();
Remote remote = new ConcreteRemote(tv);
remote.power();
remote.volumeUp();
remote.turnChannel(5);
}
}
组合模式
组合模式是一种非常有用的设计模式,它可以帮助我们处理具有复杂层次结构的对象,并使代码更加灵活且易于扩展。在实现组合模式时,我们需要定义一个公共接口或抽象类,并确保容器节点和叶节点都实现了该接口或抽象类,以便客户端可以以统一的方式处理它们。然后,我们可以使用递归的方法来处理整个层次结构,并在需要时添加新的节点类型 或修改现有的节点类型。
定义了一个 Component 接口,其中包含了一个抽象方法 draw()。
public interface Component {
void draw();
}
两个具体的实现类
叶节点
public class Rectangle implements Component {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
容器节点
在 Composite 类中,我们声明了一个私有变量 components,它用于存储该容器节点所包含的所有子节点。我们还定义了 add() 和 remove() 方法,用于添加和删除子节点。最后,我们实现了 draw() 方法,它通过递归调用所有子节点的 draw() 方法来绘制整个组合结构。
public class Composite implements Component {
private List<Component> components = new ArrayList<>();
public void add(Component component) {
components.add(component);
}
public void remove(Component component) {
components.remove(component);
}
@Override
public void draw() {
for (Component component : components) {
component.draw();
}
}
}
测试
public class Demo {
public static void main(String[] args) {
Component leaf1 = new Rectangle();
Component leaf2 = new Rectangle();
Component leaf3 = new Rectangle();
Composite composite1 = new Composite();
composite1.add(leaf1);
composite1.add(leaf2);
Composite composite2 = new Composite();
composite2.add(composite1);
composite2.add(leaf3);
composite2.draw();
}
}
在上面的示例程序中,我们首先创建了三个叶节点对象(即矩形)。然后,我们创建了两个容器节点对象(即组合),并将叶节点和其他容器节点添加到其中。最后,我们调用了顶层容器节点的 draw() 方法来绘制整个组合结构
Drawing a rectangle Drawing a rectangle Drawing a rectangle
享元模式
当我们需要创建大量相似对象时,如果每个对象都独立创建 ,可能会占用大量的内存和 CPU 资源。享元模式就是一种通过共享对象来减少内存和 CPU 资源消耗的设计模式。
在享元模式中,我们将具有相同或相似属性的对象分为两类:可共享的(即享元对象)和不可共享的(即非享元对象)。对于可共享的对象,我们只需要创建一个实例,并在需要时通过引用来共享它;而对于不可共享的对象,则需要单独创建。
假设我们正在编写一个图书馆管理系统,其中包含了大量的图书对象。每个图书对象都包含了一些基本属性,如书名、作者、出版社等。我们可以使用享元模式来避免重复创建具有相同属性的图书对象,以减少内存和 CPU 资源消耗。
首先,定义一个 Book 接口,其中包含了所有图书对象的共同属性和行为:
public interface Book {
void display();
}
具体的图书类
public class ConcreteBook implements Book {
private String title;
private String author;
private String publisher;
public ConcreteBook(String title, String author, String publisher) {
this.title = title;
this.author = author;
this.publisher = publisher;
}
@Override
public void display() {
System.out.println("Title: " + title);
System.out.println("Author: " + author);
System.out.println("Publisher: " + publisher);
}
}
创建一个享元工厂类,用于管理所有可共享的图书对象
public class BookFactory {
private static Map<String, Book> bookMap = new HashMap<>();
public static Book getBook(String title, String author, String publisher) {
String key = title + "-" + author + "-" + publisher;
Book book = bookMap.get(key);
if (book == null) {
book = new ConcreteBook(title, author, publisher);
bookMap.put(key, book);
}
return book;
}
}
Demo
public class Demo {
public static void main(String[] args) {
Book book1 = BookFactory.getBook("Design Patterns", "Gang of Four", "Addison-Wesley");
Book book2 = BookFactory.getBook("Design Patterns", "Gang of Four", "Addison-Wesley");
Book book3 = BookFactory.getBook("Effective Java", "Joshua Bloch", "Addison-Wesley");
book1.display();
System.out.println();
book2.display();
System.out.println();
book3.display();
System.out.println();
}
}
输出结果
Title: Design Patterns Author: Gang of Four Publisher: Addison-Wesley
Title: Design Patterns Author: Gang of Four Publisher: Addison-Wesley
Title: Effective Java Author: Joshua Bloch
Publisher: Addison-Wesley
享元模式可以帮助我们减少重复创建具有相似属性的对象所消耗的内存和 CPU 资源。在实现享元模式时,我们需要将具有相同或相似属性的对象分为可共享的和不可共享的两类,并使用共享池来管理所有可共享的对象。然后,在需要获取可共享对象时,我们只需要从共享池中查找即可。
行为型模式
行为型模式(Behavioral Pattern):关注于对象的行为问题,是对在不同的对象之间划分责任和算法的抽象化;不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
(11 种)策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
策略模式
允许在运行时动态地选择算法或行为的实现方式。它定义了一系列算法或行为,将它们封装在独立的类中,并且使它们之间可以互相替换。这样,客户端就可以根据不同的需求和条件选择不同的算法或行为。
在策略模式中,通常有三个角色:上下文(Context)、策略(Strategy)和具体策略(Concrete Strategy)。上下文是使用策略的客户端,在运行时可以动态地切换策略;策略是一个接口或抽象类,定义了所有支持的算法或行为;具体策略是策略的具体实现,实现了策略接口或抽象类中定义的方法。
当客户端需要改变策略时,它只需要将新的具体策略交给上下文,而不需要修改上下文的代码。这种方式既符合开放-封闭原则(Open-Closed Principle),也提高了代码的复用性和可维护性。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
示例:使用策略类实现加减乘除
public interface Strategy {
//操作方法
int doOperation(int n1,int n2);
}
四个实现,分别是加减乘除
public class AddStrategy implements Strategy{
@Override
public int doOperation(int n1, int n2) {
return n1+n2;
}
}
public class SubtractStrategy implements Strategy{
@Override
public int doOperation(int n1, int n2) {
return n1-n2;
}
}
public class MultiplyStrategy implements Strategy {
@Override
public int doOperation(int n1, int n2) {
return n1*n2;
}
}
public class DivideStrategy implements Strategy{
@Override
public int doOperation(int n1, int n2) {
if (n2!=0){
return n1/n2;
}
throw new ArithmeticException("除数为0");
}
}
Context 类
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int exec(int n1,int n2){
return strategy.doOperation(n1,n2);
}
}
测试
public class Demo {
public static void main(String[] args) {
//+
Context context = new Context(new AddStrategy());
System.out.println(context.exec(3,2));
//-
context = new Context(new SubtractStrategy());
System.out.println(context.exec(3,2));
//*
context = new Context(new MultiplyStrategy());
System.out.println(context.exec(3,2));
///
context = new Context(new DivideStrategy());
System.out.println(context.exec(3,2));
}
}
5 1 6 1
模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
**缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
模板方法一般设置成 final,防止子类重写
示例:假设我们要制作一杯咖啡,流程包括煮水、冲泡咖啡粉、倒入杯中、加糖和牛奶等步骤。其中煮水、倒入杯中和加糖这些步骤对于所有咖啡来说是相同的,而冲泡咖啡粉和加牛奶这些步骤则因种类不同而有所区别。
public abstract class Coffee {
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addSugarAndMilk();
}
protected void boilWater() {
System.out.println("烧开水");
}
protected abstract void brew();
protected void pourInCup() {
System.out.println("将开水倒入杯中");
}
protected void addSugarAndMilk() {
System.out.println("添加糖和牛奶");
}
}
美式
public class Americano extends Coffee {
@Override
protected void brew() {
System.out.println("酿造美式咖啡");
}
}
拿铁
public class Latte extends Coffee {
@Override
protected void brew() {
System.out.println("酿造浓缩咖啡");
}
@Override
protected void addSugarAndMilk() {
System.out.println("加糖和热牛奶");
}
}
测试
public class Demo {
public static void main(String[] args) {
Coffee americano = new Americano();
americano.prepareRecipe();
System.out.println();
Coffee latte = new Latte();
latte.prepareRecipe();
}
}
烧开水 酿造美式咖啡 将开水倒入杯中 添加糖和牛奶
烧开水 酿造浓缩咖啡 将开水倒入杯中 加糖和热牛奶
观察者模式
它允许对象之间建立一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。适用于一对多的情况
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
举例:在一个房间里有一个温度计和多个人,这些人希望了解房间内的温度。我们可以使用观察者模式来实现这种场景,将温度计作为被观察者(Subject),每个人作为观察者(Observer)。当温度计测量到温度变化时,它会通知所有观察者并告诉他们当前的温度。这个过程中,观察者不需要主动去查询温度计的状态,而是等待被动接收通知。这就是观察者模式的核心思想。
被观察者
public class Subject {
private List<Observer> observers = new ArrayList<>(); // 观察者列表
private int temperature; // 温度
public void attach(Observer observer) {
observers.add(observer); // 添加观察者
}
public void detach(Observer observer) {
observers.remove(observer); // 移除观察者
}
public void setTemperature(int temperature) {
this.temperature = temperature; // 设置温度
notifyObservers(); // 通知所有观察者
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature); // 更新观察者状态
}
}
}
观察者
public interface Observer {
void update(int temperature); // 更新状态
}
观察者实现类
public class Person implements Observer {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public void update(int temperature) {
System.out.println(name + "收到了温度变化通知,当前温度为:" + temperature);
}
}
测试
public static void main(String[] args) {
Subject thermometer = new Subject(); // 创建被观察者
Person alice = new Person("Alice"); // 创建观察者
Person bob = new Person("Bob");
Person charlie = new Person("Charlie");
thermometer.attach(alice); // 将观察者添加到被观察者的观察者列表中
thermometer.attach(bob);
thermometer.attach(charlie);
thermometer.setTemperature(25); // 设置温度,通知所有观察者
thermometer.setTemperature(28);
thermometer.detach(bob); // 将 Bob 移除观察者列表
thermometer.setTemperature(30);
}
Alice 收到了温度变化通知,当前温度为:25 Bob 收到了温度变化通知,当前温度为:25 Charlie 收到了温度变化通知,当前温度为:25 Alice 收到了温度变化通知,当前温度为:28 Bob 收到了温度变化通知,当前温度为:28 Charlie 收到了温度变化通知,当前温度为:28 Alice 收到了温度变化通知,当前温度为:30 Charlie 收到了温度变化通知,当前温度为:30
在这个示例代码中,Subject 类表示被观察者,它维护了一个观察者列表(observers)和一个温度(temperature)。它提供了 attach() 和 detach() 方法,用于添加和移除观察者;setTemperature() 方法用于设置温度,并在温度发生变化时通知所有观察者。notifyObservers() 方法遍历观察者列表,调用每个观察者的 update() 方法,将当前温度作为参数传递给它们
Observer 接口表示观察者,它只有一个 update() 方法,用于更新观察者状态。在这个示例中,Person 类实现了 Observer 接口,表示观察者
- Java 中也有现成的观察者类但是从 JDK8 之后
Observer
和Observable
被标记为弃用状态
迭代器模式
它允许我们遍历一个集合中的元素而无需暴露其内部结构。使用迭代器模式可以将遍历过 程与具体的集合实现分离开来,从而提高代码的灵活性和可维护性。
在 Java 中,迭代器模式通常由 Iterator 接口和对应的实现类来实现。Iterator 接口定义了一组标准的方法,包括 hasNext() 和 next(),用于检查当前位置是否有下一个元素并返回下一个元素。我们可以通过调用集合对象的 iterator() 方法来获取一个迭代器对象,然后使用 while 循环结合 hasNext() 和 next() 方法来遍历集合中的元素。
public class Book {
private String title;
public Book(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
public class BookCollection implements Iterable<Book> {
private ArrayList<Book> books = new ArrayList<>();
public void addBook(Book book) {
books.add(book);
}
public void removeBook(Book book) {
books.remove(book);
}
@Override
public Iterator<Book> iterator() {
return new BookIterator();
}
private class BookIterator implements Iterator<Book> {
private int currentPosition = 0;
@Override
public boolean hasNext() {
return currentPosition < books.size();
}
@Override
public Book next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Book nextBook = books.get(currentPosition);
currentPosition++;
return nextBook;
}
}
}
public static void main(String[] args) {
Book book1 = new Book("Effective Java");
Book book2 = new Book("Clean Code");
BookCollection collection = new BookCollection();
collection.addBook(book1);
collection.addBook(book2);
Iterator<Book> iterator = collection.iterator();
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println(book.getTitle());
}
}
责任链模式
它可以让多个对象都有机会处理请求,从而避免了请求发送者和接收者之间的耦合关系。这些对象被组织成一条链,并且沿着这条链传递请求,直到有一个对象处理它。
在责任链模式中,每个对象都持有下一个对象的引用。当一个对象收到请求时,它可以自己处理请求,也可以将请求传递给下一个对象。这个过程一直持续到请求被处理,或者传递到链的末尾。
使用责任链模式的主要优点是促进了对象之间的松散耦合,因为发送者不需要知道处理请求的具体对象。此外,该模式还允许你在运行时添加或删除链中的对象,使得它非常灵活。
package com.mqb.design.chainOfResponsibility;
public abstract class Logger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
protected Logger nextLogger;
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
protected abstract void write(String message);
}
日志实现类分别是 ConsoleLogger、FileLogger、ErrorLogger
public class ConsoleLogger extends Logger {
public ConsoleLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Console Logger: " + message);
}
}
public class FileLogger extends Logger {
public FileLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File Logger: " + message);
}
}
public class ErrorLogger extends Logger {
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
Demo
public static void main(String[] args) {
Logger consoleLogger = new ConsoleLogger(Logger.DEBUG);
Logger fileLogger = new FileLogger(Logger.INFO);
Logger errorLogger = new ErrorLogger(Logger.ERROR);
// 手动构建责任链,也可以使用工厂或者依赖注入等方式自动创建
consoleLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(errorLogger);
// 测试日志输出
consoleLogger.logMessage(Logger.DEBUG, "This is a debug message.");
consoleLogger.logMessage(Logger.INFO, "This is an information message.");
consoleLogger.logMessage(Logger.ERROR, "This is an error message.");
}
命令模式
它将请求封装成一个对象并传递给调用对象,使得不同的请求可以使用相同的调用方式进行处理。命令模式的核心思想是将请求和执行分离开来,使得请求发起者和请求执行者之间解耦,从而达到松耦合的目的。
在命令模式中,将请求封装成一个命令对象,该对象包含了接收者、具体操作以及执行操作的方法。然后,将该命令对象传递给一个调用对象,该调用对象负责对命令对象进行存储、传递和执行等操作。这样,调用对象可以根据需要调用不同的命令对象,实现对请求的多样化处理。
命令模式的优点是可以方便地扩展新的命令,同时也可以方便地修改或删除已有的命令,从而提高系统的灵活性和可维护性。缺点是可能会导致命令对象的数量过多,增加系统的复杂度。
public class Light {
public void turnOn() {
System.out.println("The light is on.");
}
public void turnOff() {
System.out.println("The light is off.");
}
}
public interface Command {
/**
* 执行方法
*/
void execute();
}
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
/**
* 关灯命令
* @author qingbomy
*/
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
}
public class RemoteControl {
private List<Command> commands = new ArrayList<>();
public void setCommand(Command command) {
commands.add(command);
}
public void executeCommands() {
for (Command command : commands) {
command.execute();
}
commands.clear();
}
}
public class Demo {
public static void main(String[] args) {
// 创建一个遥控器
RemoteControl remoteControl = new RemoteControl();
// 创建一个灯对象
Light light = new Light();
// 创建一个开灯命令并 设置给遥控器
Command lightOnCommand = new LightOnCommand(light);
remoteControl.setCommand(lightOnCommand);
// 创建一个关灯命令并设置给遥控器
Command lightOffCommand = new LightOffCommand(light);
remoteControl.setCommand(lightOffCommand);
// 执行命令
remoteControl.executeCommands();
}
}
The light is on. The light is off.
备忘录模式
它允许在不破坏封装性的前提下捕获一个对象的内部状态,并在之后将该对象恢复到先前保存的状态。这可以用于撤销操作或者维护历史记录等场景。
备忘录模式通常由三个角色组成:
- 发起人角色(Originator):创建并持有一个备忘录对象,用于保存其内部状态。
- 备忘录角色(Memento):保存发起人角色的内部状态。
- 管理者角色(Caretaker):负责保存和恢复发起人角色的状态。
// 备忘录类
class Memento {
private final String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 发起人
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 保存状态到备忘录
* @return
*/
public Memento saveStateToMemento() {
return new Memento(state);
}
/**
* 从备忘录恢复状态
* @param memento
*/
public void restoreStateFromMemento(Memento memento) {
state = memento.getState();
}
}
// 管理者类
class Caretaker {
private Memento memento;
/**
* 设置备忘录
* @param memento
*/
public void setMemento(Memento memento) {
this.memento = memento;
}
/**
* 获取备忘录
* @return
*/
public Memento getMemento() {
return memento;
}
}
示例
// 示例
public class MementoPatternExample {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// 设置初始状态
originator.setState("State A");
System.out.println("Current state: " + originator.getState());
// 保存状态
caretaker.setMemento(originator.saveStateToMemento());
// 修改状态
originator.setState("State B");
System.out.println("Current state: " + originator.getState());
// 恢复状态
originator.restoreStateFromMemento(caretaker.getMemento());
System.out.println("Current state: " + originator.getState());
}
}
Current state: State A Current state: State B Current state: State A
状态模式
它允许对象在内部状态发生改变时改变其行为。该模式将对象的状态封装成不同的类,并且在不同状态下表现出不同的行为。这样可以避免使用大量的 if-else 语句来判断对象当前的状态,并且使得状态转换更加清晰和易于维护。
在状态模式中,通常会定义一个抽象状态类和多个具体状态类。抽象状态类定义了状态接口,具体状态类实现了该接口并提供了具体的状态行为。此外,Context 类也是重要的组成部分,它持有当前状态的引用,并且根据不同状态调用相应的方法。
状态模式主要优点包括:将状态转换逻辑和状态行为都封装到具体的状态类中,简化了代码结构;状态类之间相互独立,增强了系统的可扩展性和灵活性;利用多态性和依赖倒置原则,降低了耦合度,提高了代码的可维护性和可读性。
但是,状态模式也存在一些缺点。首先,在状态比较多时,会产生很多状态类,这会导致类的数量增加,不利于整个系统的管理。其次,在状态转换比较复杂时,可能需要多次切换状态才能达到目标状态,这会增加系统的复杂度。
public abstract class State {
protected Context context;
public void setContext(Context context) {
this.context = context;
}
public abstract void handle();
}
// 上下文类
public class Context {
private State state;
public Context(State state) {
setState(state);
}
public void setState(State state) {
this.state = state;
this.state.setContext(this);
}
public void request() {
this.state.handle();
}
}
// 具体状态类A
public class ConcreteStateA extends State {
@Override
public void handle() {
System.out.println("当前状态为A");
// 状态转换
this.context.setState(new ConcreteStateB());
}
}
// 具体状态类B
public class ConcreteStateB extends State {
@Override
public void handle() {
System.out.println("当前状态为B");
// 状态转换
this.context.setState(new ConcreteStateC());
}
}
// 具体状态类C
public class ConcreteStateC extends State {
@Override
public void handle() {
System.out.println("当前状态为C");
// 状态转换
this.context.setState(new ConcreteStateA());
}
}
测试
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
context.request();
context.request();
context.request();
}
当前状态为 A 当前状态为 B 当前状态为 C
访问者模式
允许你定义一个独立于对象的操作,而这些操作可以应用于不同类型的对象。在该模式中,我们将操作封装在一个访问者类中,然后在被访问的类中使用该访问者进行处理。
下面举一个通俗的例子:假设我们有一个动物园,里面有许多不同类型的动物,例如大象、狮子和猴子。我们想要对这些动物进行分类,可以使用访问者模式实现。
首先,我们需要定义访问者接口(AnimalVisitor),其中包含了对每种具体动物的访问方法。然后,我们定义抽象动物类(Animal),其中包含了 accept 方法,用于接受访问者的访问。
最后,我们实现具体的动物类(Elephant、Lion 和 Monkey),并分别实现 accept 方法,在其中调用访问者的访问方法。
// 访问者接口
interface AnimalVisitor {
void visit(Elephant elephant);
void visit(Lion lion);
void visit(Monkey monkey);
}
// 抽象动物类
public abstract class Animal {
abstract void accept(AnimalVisitor visitor);
}
// 具体动物类
public class Elephant extends Animal {
@Override
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
public class Lion extends Animal {
@Override
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
public class Monkey extends Animal {
@Override
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
// 具体的访问者类
public class AnimalClassifier implements AnimalVisitor {
private String classification;
@Override
public void visit(Elephant elephant) {
classification = "Herbivore";
}
@Override
public void visit(Lion lion) {
classification = "Carnivore";
}
@Override
public void visit(Monkey monkey) {
classification = "Omnivore";
}
public String getClassification() {
return classification;
}
}
测试
public static void main(String[] args) {
List<Animal> animals = new ArrayList<>();
animals.add(new Elephant());
animals.add(new Lion());
animals.add(new Monkey());
AnimalClassifier classifier = new AnimalClassifier();
for (Animal animal : animals) {
animal.accept(classifier);
System.out.println(animal.getClass().getSimpleName() + ": " + classifier.getClassification());
}
}
Elephant: Herbivore Lion: Carnivore Monkey: Omnivore
中介者模式
中介者模式是一种对象间通信的设计模式。在该模式中,多个对象之间不直接相互通信,而是通过一个中介者对象来协调它们之间的交互。这个中介者对象负责处理所有相关对象之间的通信和协调,并将其转发给其他对象。
例如,在一个聊天室应用程序中,有多个用户可以发送消息到聊天室内。如果每个用户都直接与其他用户通信,则实现起来非常复杂。但是,如果使用中介者模式,则可以创建一个中介者对象,负责协调聊天室内所有用户之间的通信。
public abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name){
this.mediator = mediator;
this.name = name;
}
public abstract void send(String message);
public abstract void receive(String message);
}
public class ChatUser extends User {
public ChatUser(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message) {
System.out.println(this.name + " sends: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + " receives: " + message);
}
}
public interface ChatMediator {
public void sendMessage(String message, User user);
public void addUser(User user);
}
public class ChatMediatorImpl implements ChatMediator {
private List<User> users;
public ChatMediatorImpl(){
this.users=new ArrayList<>();
}
@Override
public void sendMessage(String message, User sender) {
for(User user : this.users){
if(user != sender){
user.receive(message);
}
}
}
@Override
public void addUser(User user){
this.users.add(user);
}
}
public static void main(String[] args) {
ChatMediator mediator = new ChatMediatorImpl();
User user1 = new ChatUser(mediator, "Alice");
User user2 = new ChatUser(mediator, "Bob");
User user3 = new ChatUser(mediator, "Charlie");
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("Hello from Alice!");
user2.send("Hi, Bob here.");
}
Alice sends: Hello from Alice! Bob receives: Hello from Alice! Charlie receives: Hello from Alice! Bob sends: Hi, Bob here. Alice receives: Hi, Bob here. Charlie receives: Hi, Bob here.
解释器模式
它用于处理特定类型的语言语法或运算表达式。该模式定义了一个表达式接口,该接口有一个解释方法,可以使用该方法对表达式进行解释。
在解释器模式中,我们将表达式分成两类:终端表达式和非终端表达式。终端表达式表示最基本的表达式,而非终端表达式表示由其他表达式组合而成的复杂表达式。
interface Expression {
boolean interpret(String context);
}
public class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
public class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
public class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}
public class InterpreterPatternDemo {
//规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
//规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married woman? " + isMarriedWoman.interpret("Married Julie"));
}
}