Java设计模式
- 一、观察者设计模式
- 1.1 概述
- 1.2 结构
- 1.3 特点
- 1. 优点
- 2. 缺点
- 3. 使用场景
- 1.4 JDK中的实现
- 1. Observable 类
- 2. Observer 接口
- 3. 例子
- 二、模板设计模式
- 三、单例设计模式
- 一、懒汉式单例
- 二、饿汉式单例
- 四、Builder模式
- 4.1 概述
- 4.2 结构
- 4.3 具体实现
- 4.4 使用场景
- 五、责任链设计模式
- 5.1 定义:
- 5.2 结构:
- 5.3 举例
- 5.4 应用场景
- 六、策略模式
一、观察者设计模式
1.1 概述
观察者模式(Observer Pattern),又称发布-订阅模式,是一种行为设计模式。它定义了一种一对多的依赖关系,允许多个观察者对象同时监听某个主题对象(Subject)。当主题对象的状态发生变化时,它会通知所有注册的观察者,以便这些观察者能够自动更新自身的状态。
1.2 结构
观察者模式通常包含以下角色:
- Subject(主题):
- 抽象主题角色,负责维护观察者的列表。
- 提供接口以添加和删除观察者。
- 通常包括方法如
attach(observer)
和detach(observer)
。
- ConcreteSubject(具体主题):
- 实现了 Subject 接口的具体类,维护有关状态。
- 当状态变化时,调用通知方法以更新所有观察者。
- Observer(观察者):
- 抽象观察者角色,定义更新接口,通常是
update()
方法。 - 观察者在收到主题变化的通知时调用该方法更新自身状态。
- 抽象观察者角色,定义更新接口,通常是
- ConcreteObserver(具体观察者):
- 实现 Observer 接口的具体类。
- 在更新方法中实现如何响应主题变化。
例子:
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景
- 微信用户就是观察者
- 微信公众号是被观察者
有多个的微信用户关注了这个公众号。
// 观察者接口
public interface Observer {void update(String message); // 接收更新消息
}// 主题接口
public interface Subject {void attach(Observer observer); // 添加观察者void detach(Observer observer); // 删除观察者void notifyObservers(String message); // 通知观察者更新消息
}// 具体主题类
import java.util.ArrayList;
import java.util.List;public class WeChatOfficialAccount implements Subject {private List<Observer> observers = new ArrayList<>(); // 观察者列表@Overridepublic void attach(Observer observer) {observers.add(observer);System.out.println(observer + " 关注了公众号。");}@Overridepublic void detach(Observer observer) {observers.remove(observer);System.out.println(observer + " 取消关注了公众号。");}@Overridepublic void notifyObservers(String message) {System.out.println("公众号发布新内容: " + message);for (Observer observer : observers) {observer.update(message);}}
}// 具体观察者类
public class WeChatUser implements Observer {private String name;public WeChatUser(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " 收到了新推送: " + message);}@Overridepublic String toString() {return name; // 方便打印}
}// 示例用法
public class WeChatObserverPattern {public static void main(String[] args) {// 创建公众号WeChatOfficialAccount wechatAccount = new WeChatOfficialAccount();// 创建微信用户WeChatUser user1 = new WeChatUser("用户A");WeChatUser user2 = new WeChatUser("用户B");WeChatUser user3 = new WeChatUser("用户C");// 用户关注公众号wechatAccount.attach(user1);wechatAccount.attach(user2);wechatAccount.attach(user3);// 发布新内容wechatAccount.notifyObservers("今天的天气真不错!");// 用户取消关注wechatAccount.detach(user2);// 再次发布新内容wechatAccount.notifyObservers("明天有一场特别活动!");}
}
1.3 特点
1. 优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有注册的观察者都会收到信息(可以实现广播机制)。
2. 缺点
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时。
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃。
3. 使用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。
1.4 JDK中的实现
在Java中,通过iava.util. Observable类和java.util. observer接口定义了观察者模式,只要实现它们的子类就可以编写 观察者模式实例。
1. Observable 类
Observable
类是抽象目标类(被观察者),它有一个 Vector
集合成员变量,用于保存所有要通知的观察者对象。它的三个重要方法如下:
-
void addObserver(Observer o)
用于将新的观察者对象添加到集合中。 -
void notifyObservers(Object arg)
调用集合中的所有观察者对象的update
方法,通知它们数据发生改变。通常,越晚加入集合的观察者越先得到通知。 -
void setChanged()
用来设置一个boolean
类型的内部标志,注明目标对象发生了变化。当它为true
时,notifyObservers()
才会通知观察者。
2. Observer 接口
Observer
接口是抽象观察者,它监视目标对象的变化。当目标对象发生变化时,观察者会得到通知,并调用 update
方法进行相应的工作。
3. 例子
import java.util.Observable;
import java.util.Observer;// 具体的被观察者类
class WeatherData extends Observable {private float temperature;public void setTemperature(float temperature) {this.temperature = temperature;setChanged(); // 标记状态已更改notifyObservers(temperature); // 通知观察者}
}// 具体的观察者类
class CurrentConditionsDisplay implements Observer {private float temperature;@Overridepublic void update(Observable o, Object arg) {if (o instanceof WeatherData) {this.temperature = (float) arg; // 更新温度display(); // 显示当前条件}}public void display() {System.out.println("Current temperature: " + temperature + " degrees.");}
}// 主程序
public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();weatherData.addObserver(currentDisplay); // 注册观察者weatherData.setTemperature(25.5f); // 更新温度,触发通知weatherData.setTemperature(30.0f); // 再次更新温度}
}
二、模板设计模式
模板方法设计模式( Template Method Design Pattern ),在一个方法中定义一个算法骨架(即模板),并将某些步骤推迟到子类中实现。
该模式在父类中定义一个方法的骨架(或算法的框架),并允许子类在不改变算法结构的情况下重新定义该算法的某些步骤。
举例:
- 创建一个抽象类,定义算法骨架:
// 抽象类,定义制作饮料的模板方法
abstract class Beverage {// 模板方法,定义了制作饮料的通用步骤public final void prepareRecipe() {boilWater();brew();pourInCup();if (customerWantsCondiments()) { // 钩子方法,子类可以选择重写addCondiments();}}// 具体步骤:烧水private void boilWater() {System.out.println("Boiling water");}// 具体步骤:倒入杯中private void pourInCup() {System.out.println("Pouring into cup");}// 抽象步骤:冲泡饮料,具体实现由子类提供protected abstract void brew();// 抽象步骤:添加调料,具体实现由子类提供protected abstract void addCondiments();// 钩子方法:决定是否添加调料,子类可以选择重写protected boolean customerWantsCondiments() {return true; // 默认添加调料}
}
- 创建具体的子类,实现抽象类中定义的抽象方法:
// 具体类:茶
class Tea extends Beverage {@Overrideprotected void brew() {System.out.println("Steeping the tea");}@Overrideprotected void addCondiments() {System.out.println("Adding lemon");}// 重写钩子方法,茶默认不添加调料@Overrideprotected boolean customerWantsCondiments() {return false;}
}
// 具体类:咖啡
class Coffee extends Beverage {@Overrideprotected void brew() {System.out.println("Dripping coffee through filter");}@Overrideprotected void addCondiments() {System.out.println("Adding sugar and milk");}
}
- 客户端代码:
// 客户端代码
public class TemplateMethodPatternExample {public static void main(String[] args) {System.out.println("Preparing tea:");Beverage tea = new Tea();tea.prepareRecipe();System.out.println("\nPreparing coffee:");Beverage coffee = new Coffee();coffee.prepareRecipe();}
}
- 输出:
Preparing tea:
Boiling water
Steeping the tea
Pouring into cupPreparing coffee:
Boiling water
Dripping coffee through filter
Pouring into cup
Adding sugar and milk
Beverage
抽象类定义了模板方法prepareRecipe
,并封装了制作饮料的固定步骤brew()
和addCondiments()
是由子类实现的抽象方法,各自实现了茶和咖啡的制作过程- 钩子方法
customerWantsCondiments()
允许子类控制是否执行某个步骤
三、单例设计模式
单例模式有以下特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
一、懒汉式单例
在第一次访问时实例化自己
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {private Singleton() {}private static Singleton single=null;//静态工厂方法 public static Singleton getInstance() {if (single == null) { single = new Singleton();} return single;}
}
Singleton
通过将构造方法限定为private
避免了类在外部被实例化,在同一个虚拟机范围内,Singleton
的唯一实例只能通过getInstance()
方法访问。
如果走到single == null
时发生线程切换,会存在线程不安全的问题。
有三种方式确保懒汉式单例的线程安全问题:
- 在getInstance上加上同步:
public static synchronized Singleton getInstance() {if (single == null) { single = new Singleton();} return single;
}
- 双重检查锁定
public static Singleton getInstance() {if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton;
}
- 静态内部类
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; }
}
- 静态内部类的特点:
LazyHolder
是一个静态内部类,它只有在第一次被访问时才会被加载和初始化。- 类加载是线程安全的,JVM 在加载类时会确保类加载的线程安全性。
INSTANCE
的初始化:LazyHolder
内的INSTANCE
是一个static final
的变量,它在LazyHolder
被加载时完成初始化。static final
保证了INSTANCE
的初始化只会发生一次,并且由 JVM 保证线程安全。
- 特点:延迟加载,线程安全,利用类加载机制实现,推荐使用。
第三种比1、2好,即实现了线程安全,又避免了同步带来的性能影响
二、饿汉式单例
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {private Singleton1() {}private static final Singleton1 single = new Singleton1();//静态工厂方法 public static Singleton1 getInstance() {return single;}
}
- 特点:线程安全,类加载时初始化,简单实现,但可能会造成资源浪费。
四、Builder模式
4.1 概述
Builder 模式是一种创建型设计模式,通过逐步构建复杂对象,将对象的构建过程与表示分离,使得同样的构建过程可以创建不同的对象。
特点:
- 将对象构建与其表示分离:
- 构建复杂对象时,不直接实例化对象,而是使用 Builder 逐步设置属性,最后通过 build() 方法生成对象。
- 灵活性和可扩展性:
- 新增属性或功能只需修改 Builder 类,而无需更改已有的构造代码。
- 链式调用:
- Builder 模式允许链式设置属性,代码可读性更强。
4.2 结构
- 产品类(Product):
- 要创建的复杂对象。
- 包含多个属性,通常是不可变的。
- 构建器类(Builder):
- 定义构建对象的步骤,每个步骤设置对象的一个属性。
- 提供
build()
方法返回完整的产品对象。
- 具体构建器(ConcreteBuilder):
- 实现构建器类,包含构建对象的具体逻辑。
- 指导者类(Director,可选):
- 用于控制构建过程的顺序和逻辑。
- 实际应用中常常省略,由客户端直接调用 Builder。
4.3 具体实现
比如我们现在要自定义一个http发送请求连接:
传统的方式(构造函数):
http对象代码为:
public class HttpRequest {private String url;private String method;private Map<String, String> headers;private String body;public HttpRequest(String url, String method, Map<String, String> headers, String body) {this.url = url;this.method = method;this.headers = headers != null ? headers : new HashMap<>();this.body = body;}@Overridepublic String toString() {return "HttpRequest { " +"url='" + url + '\'' +", method='" + method + '\'' +", headers=" + headers +", body='" + body + '\'' +" }";}
}
构建方式:
public class Main {public static void main(String[] args) {// 构建请求Map<String, String> headers = new HashMap<>();headers.put("Content-Type", "application/json");headers.put("Authorization", "Bearer token123");HttpRequest request = new HttpRequest("https://api.example.com/data", // URL"POST", // 方法headers, // Headers"{\"key\":\"value\"}" // Body);System.out.println(request);}
}
问题分析
- 构造器参数过多时,易混淆或出错,特别是可选参数场景。
- 代码可读性差
传统方式(Set函数)
http对象代码:
public class HttpRequest {private String url;private String method;private Map<String, String> headers = new HashMap<>();private String body;public void setUrl(String url) {this.url = url;}public void setMethod(String method) {this.method = method;}public void setHeader(String key, String value) {this.headers.put(key, value);}public void setBody(String body) {this.body = body;}@Overridepublic String toString() {return "HttpRequest { " +"url='" + url + '\'' +", method='" + method + '\'' +", headers=" + headers +", body='" + body + '\'' +" }";}
}
构建方式:
public class Main {public static void main(String[] args) {// 构建请求HttpRequest request = new HttpRequest();request.setUrl("https://api.example.com/data");request.setMethod("POST");request.setHeader("Content-Type", "application/json");request.setHeader("Authorization", "Bearer token123");request.setBody("{\"key\":\"value\"}");System.out.println(request);}
}
问题分析:
- 缺点: 无法保证对象的完整性(可能遗漏某些关键参数),容易导致不一致的状态
为什么改用 Builder 模式
- 参数过多且部分可选时,传统方式可读性差。
- Builder 模式可以链式调用,代码更清晰。
- Builder 可封装复杂的初始化逻辑,确保对象的一致性和不可变性。
改进后的builder:
- PRODUCT类
public class HttpRequest {private String url; // 必填private String method; // 必填private Map<String, String> headers; // 可选private String body; // 可选// 私有构造器,确保只能通过 Builder 创建private HttpRequest(String url, String method, Map<String, String> headers, String body) {this.url = url;this.method = method;this.headers = headers;this.body = body;}// Getterspublic String getUrl() { return url; }public String getMethod() { return method; }public Map<String, String> getHeaders() { return headers; }public String getBody() { return body; }
}
- 构建器类(Builder)
public interface Builder {Builder setUrl(String url);Builder setMethod(String method);Builder addHeader(String key, String value);Builder setBody(String body);HttpRequest build();
}
- 具体构建器(ConcreteBuilder)
import java.util.HashMap;
import java.util.Map;public class HttpRequestBuilder implements Builder {private String url;private String method;private Map<String, String> headers = new HashMap<>();private String body;@Overridepublic Builder setUrl(String url) {this.url = url;return this;}@Overridepublic Builder setMethod(String method) {this.method = method;return this;}@Overridepublic Builder addHeader(String key, String value) {this.headers.put(key, value);return this;}@Overridepublic Builder setBody(String body) {this.body = body;return this;}@Overridepublic HttpRequest build() {// 校验必填参数if (url == null || method == null) {throw new IllegalStateException("URL and Method are required!");}return new HttpRequest(url, method, headers, body);}
}
- 指导者类(Director,可选)
public class HttpRequestDirector {private final Builder builder;public HttpRequestDirector(Builder builder) {this.builder = builder;}public HttpRequest constructGetRequest(String url) {return builder.setUrl(url).setMethod("GET").build();}public HttpRequest constructPostRequest(String url, String body) {return builder.setUrl(url).setMethod("POST").setBody(body).addHeader("Content-Type", "application/json").build();}
}
- 客户端代码
public class Main {public static void main(String[] args) {// 使用 Builder 直接构建HttpRequest request1 = new HttpRequestBuilder().setUrl("https://api.example.com/resource").setMethod("GET").addHeader("Authorization", "Bearer token123").build();System.out.println("Request1: " + request1.getUrl());// 使用 Director 构建HttpRequestDirector director = new HttpRequestDirector(new HttpRequestBuilder());HttpRequest request2 = director.constructPostRequest("https://api.example.com/resource", "{\"key\":\"value\"}");System.out.println("Request2: " + request2.getBody());}
}
Builder 的优势
- 强制性:
- 必须设置关键参数(如 URL、method),否则无法创建对象。
- 校验性:
build()
方法集中校验,确保对象状态一致。
- 可读性:
- 链式调用使代码更直观,避免参数顺序错误。
通过这种方式,Builder 模式能够显著提升代码的安全性和可靠性,避免遗漏关键参数的问题。
4.4 使用场景
- 参数多且部分可选的对象构建:
- 如配置类、网络请求对象(如
oKHttp
)、数据库连接对象等。
- 如配置类、网络请求对象(如
- 不可变对象的构建:
- 需要在对象创建后禁止修改时,如
StringBuilder
或Immutable
对象。
- 需要在对象创建后禁止修改时,如
- 复杂初始化逻辑的对象:
- 如需要组合多个子对象或处理复杂逻辑的初始化。
- 跨平台对象创建:
- 如需要动态生成 JSON、XML 等结构。
- 避免构造器参数过多:
- 如果构造器参数多且顺序容易搞混,Builder 模式可以显著改善代码可读性和安全性。
五、责任链设计模式
5.1 定义:
责任链模式是一种行为设计模式,它允许多个对象按顺序处理请求,而不明确指定处理者。请求沿着链传递,直到某个对象处理它或链的末端。
责任链也可以想象成推卸责任:
假设要去公司领取资料,首先向公司前台打听要去哪里领取资料,她告诉我们去”营业窗口“。等到了”营业窗口“后,又被告知应该去”售后部门“。等我们好不容易赶到了”售后部门“,又被告知应该去”资料中心“,因此最后不得不去”资料中心“。
在找到合适的办事人之前,被不断踢给一个又一个人,这就是”推卸责任“。
这种模式,当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负载处理时,就需要推卸责任。这种情况下,可以考虑将多个对象组成一条责任链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责。
5.2 结构:
- Handler(抽象处理者)
- 定义处理请求的方法
handleRequest
。 - 保存下一个处理者的引用。
- 定义处理请求的方法
- ConcreteHandler(具体处理者)
- 实现具体的处理逻辑。
- 如果当前处理者无法处理请求,则将请求传递给下一个处理者。
- Client(客户端)
- 向第一个ConcreteHandler角色发送请求的角色,创建责任链并发送请求
5.3 举例
表单验证
- 场景:用户提交表单时,需要对不同字段进行依次验证(如非空、格式、长度等)。
- 实现:每个验证规则是责任链中的一个节点,如果当前验证失败,则返回错误;否则传递给下一个验证器。
-
定义一个抽象的处理类(Handler):
abstract class Validator {//定义下一个验证器,即推卸责任的对象protected Validator next;public void setNext(Validator next) {this.next = next;}public abstract boolean validate(String input); }
-
然后,我们创建具体的处理类(ConcreteHandler),它们继承自抽象的处理类,并实现了自己的处理逻辑validate:
class NotEmptyValidator extends Validator {@Overridepublic boolean validate(String input) {if (input == null || input.isEmpty()) {System.out.println("Field cannot be empty");return false;}return next == null || next.validate(input);} }class LengthValidator extends Validator {@Overridepublic boolean validate(String input) {if (input.length() > 10) {System.out.println("Input too long");return false;}return next == null || next.validate(input);} }
-
最后,在客户端代码中创建处理器对象,并将它们连接起来:
// 客户端代码 Validator validator = new NotEmptyValidator(); validator.setNext(new LengthValidator()); validator.validate("TestInput");
5.4 应用场景
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
六、策略模式
问题描述:
- 有各种鸭子(北京鸭、玩具鸭),鸭子有各种行为(叫、飞)
- 希望能够实现不同的鸭子,显示不同鸭子的信息
传统方法会创建一个抽象类
public abstract class Duck{public Duck(){}public abstract void display();//显示鸭子信息public void fly(){System.out.println("鸭子会飞翔");}}
这时候我们要实现具体的鸭子,如玩具鸭,得重写Duck类里的所有方法,因为玩具鸭不会飞。如果要实现北京鸭,又得重写Duck类里的所有方法,因为北京鸭飞飞行能力一般。
传统方式存在问题:
- 其他鸭子都继承了Duck类,所有fly让所有的子类都会飞了,这是不正确的
- 即继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应
这时引出策略模式
- 策略模式中,定义算法族(策略组),分别封装起来,让他们之间可以相互替换,此模式
让算法的变化独立于使用算法的客户
。
我的理解是:
把原来继承的部分换成一个插槽,这个插槽在策略模式中是一个接口。不同的类插槽处的具体实现不一样,这时候我们可以搭建很多个不一样的积木(策略组),用哪一个就把哪一个积木放到插槽里,不用对插槽进行重新改动,这样后面的其他人也可以重复使用这个积木。策略模式中具体的实现是:积木即继承了接口的实现类,使用哪一个实现了就将其赋值给接口即可,不用重新接口方法。
实例:
策略模式的原则就是,分离变化部分,封装接口,基于接口编程各种功能
。
将原来的方法定义改成策略接口(创建一个插槽)
public abstract class Duck{//属性,策略接口FlyBehavior flyBehavior;public abstract void display();//显示鸭子信息public void fly(){//改进if(flyBehavior!=null){flyBehavior.fly();}}//提供给用户,动态变化行为动作(更换插槽里的积木调用这个方法就行)public void setFlyBehavior(FlyBehavior flyBehavior){this.flyBehavior = flyBehavior;}
}
策略接口:
public interface FlyBehavior {void fly();//子类的具体实现
}
策略组的实现(实现放到接口里的积木):
public class GoodFlyBehavior implements FlyBehavior{@Overridepublic void fly() {System.out.println("飞翔技术高超");}
}
public class NoFlyFlyBehavior implements FlyBehavior{@Overridepublic void fly() {System.out.println("不会飞翔");}
}
不同鸭子的实现:
public class ToyDuck extends Duck{@Overridepublic void display() {//把想放的积木放到插槽里flyBehavior = new NoFlyFlyBehavior();}
}
public class BeiJingDuck extends Duck{@Overridepublic void display() {flyBehavior = new GoodFlyBehavior();}
}
即将项目中变化的部分分离出来,多用组合/聚合,少用继承;用行为类组合,而不是行为的继承。
体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只需要添加一种策略即可。
需要注意的是:没添加一个策略就需要增加一个类,当策略过多是会导致类数目庞大。
实例
通过定义一组算法接口(如存储操作),让不同的存储服务(OSS、MinIO)实现具体的逻辑,客户端只需依赖接口,动态选择实现即可。
// 1. 策略接口
public interface StorageStrategy {void upload(String file);
}// 2. 具体策略:OSS
public class OSSStorage implements StorageStrategy {@Overridepublic void upload(String file) {System.out.println("Uploading to OSS: " + file);}
}// 3. 具体策略:MinIO
public class MinIOStorage implements StorageStrategy {@Overridepublic void upload(String file) {System.out.println("Uploading to MinIO: " + file);}
}
// 4. 上下文类
public class StorageContext {private StorageStrategy strategy;public StorageContext(StorageStrategy strategy) {this.strategy = strategy;}public void setStrategy(StorageStrategy strategy) {this.strategy = strategy;}public void uploadFile(String file) {strategy.upload(file);}
}
// 5. 客户端使用
public class Main {public static void main(String[] args) {StorageContext context = new StorageContext(new OSSStorage());context.uploadFile("file1.txt"); // 使用 OSS 上传context.setStrategy(new MinIOStorage());context.uploadFile("file2.txt"); // 使用 MinIO 上传}
}