Java设计模式

Java设计模式

一、观察者设计模式

1.1 概述

观察者模式(Observer Pattern),又称发布-订阅模式,是一种行为设计模式。它定义了一种一对多的依赖关系,允许多个观察者对象同时监听某个主题对象(Subject)。当主题对象的状态发生变化时,它会通知所有注册的观察者,以便这些观察者能够自动更新自身的状态。

1.2 结构

观察者模式通常包含以下角色:

  1. Subject(主题)
    • 抽象主题角色,负责维护观察者的列表。
    • 提供接口以添加和删除观察者。
    • 通常包括方法如 attach(observer)detach(observer)
  2. ConcreteSubject(具体主题)
    • 实现了 Subject 接口的具体类,维护有关状态。
    • 当状态变化时,调用通知方法以更新所有观察者。
  3. Observer(观察者)
    • 抽象观察者角色,定义更新接口,通常是 update() 方法。
    • 观察者在收到主题变化的通知时调用该方法更新自身状态。
  4. ConcreteObserver(具体观察者)
    • 实现 Observer 接口的具体类。
    • 在更新方法中实现如何响应主题变化。

例子:

在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景

  • 微信用户就是观察者
  • 微信公众号是被观察者
    有多个的微信用户关注了这个公众号。
java 复制代码
// 观察者接口
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<>(); // 观察者列表

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
        System.out.println(observer + " 关注了公众号。");
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
        System.out.println(observer + " 取消关注了公众号。");
    }

    @Override
    public 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;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " 收到了新推送: " + message);
    }

    @Override
    public 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. 例子

java 复制代码
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;

    @Override
    public 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 ),在一个方法中定义一个算法骨架(即模板),并将某些步骤推迟到子类中实现。

该模式在父类中定义一个方法的骨架(或算法的框架),并允许子类在不改变算法结构的情况下重新定义该算法的某些步骤。

举例:

  • 创建一个抽象类,定义算法骨架:
java 复制代码
// 抽象类,定义制作饮料的模板方法
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; // 默认添加调料
    }
}
  • 创建具体的子类,实现抽象类中定义的抽象方法:
java 复制代码
// 具体类:茶
class Tea extends Beverage {

    @Override
    protected void brew() {
        System.out.println("Steeping the tea");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon");
    }

    // 重写钩子方法,茶默认不添加调料
    @Override
    protected boolean customerWantsCondiments() {
        return false;
    }
}
java 复制代码
// 具体类:咖啡
class Coffee extends Beverage {

    @Override
    protected void brew() {
        System.out.println("Dripping coffee through filter");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}
  • 客户端代码:
java 复制代码
// 客户端代码
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();
    }
}
  • 输出:
plaintext 复制代码
Preparing tea:
Boiling water
Steeping the tea
Pouring into cup

Preparing coffee:
Boiling water
Dripping coffee through filter
Pouring into cup
Adding sugar and milk
  • Beverage 抽象类定义了模板方法prepareRecipe,并封装了制作饮料的固定步骤
  • brew()addCondiments() 是由子类实现的抽象方法,各自实现了茶和咖啡的制作过程
  • 钩子方法 customerWantsCondiments()允许子类控制是否执行某个步骤

三、单例设计模式

单例模式有以下特点:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

一、懒汉式单例

在第一次访问时实例化自己

java 复制代码
//懒汉式单例类.在第一次调用的时候实例化自己 
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时发生线程切换,会存在线程不安全的问题。

有三种方式确保懒汉式单例的线程安全问题:

  1. 在getInstance上加上同步:
java 复制代码
public static synchronized Singleton getInstance() {
         if (single == null) {  
             single = new Singleton();
         }  
        return single;
}
  1. 双重检查锁定
java 复制代码
public static Singleton getInstance() {
        if (singleton == null) {  
            synchronized (Singleton.class) {  
               if (singleton == null) {  
                  singleton = new Singleton(); 
               }  
            }  
        }  
        return singleton; 
}
  1. 静态内部类
java 复制代码
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好,即实现了线程安全,又避免了同步带来的性能影响

二、饿汉式单例

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

java 复制代码
//饿汉式单例类.在类初始化时,已经自行实例化 
public class Singleton1 {
    private Singleton1() {}
    private static final Singleton1 single = new Singleton1();
    //静态工厂方法 
    public static Singleton1 getInstance() {
        return single;
    }
}
  • 特点:线程安全,类加载时初始化,简单实现,但可能会造成资源浪费。

四、Builder模式

4.1 概述

Builder 模式是一种创建型设计模式,通过逐步构建复杂对象,将对象的构建过程与表示分离,使得同样的构建过程可以创建不同的对象。

特点

  1. 将对象构建与其表示分离:
    • 构建复杂对象时,不直接实例化对象,而是使用 Builder 逐步设置属性,最后通过 build() 方法生成对象。
  2. 灵活性和可扩展性:
    • 新增属性或功能只需修改 Builder 类,而无需更改已有的构造代码。
  3. 链式调用:
    • Builder 模式允许链式设置属性,代码可读性更强。

4.2 结构

  1. 产品类(Product)
    • 要创建的复杂对象。
    • 包含多个属性,通常是不可变的。
  2. 构建器类(Builder)
    • 定义构建对象的步骤,每个步骤设置对象的一个属性。
    • 提供 build() 方法返回完整的产品对象。
  3. 具体构建器(ConcreteBuilder)
    • 实现构建器类,包含构建对象的具体逻辑。
  4. 指导者类(Director,可选)
    • 用于控制构建过程的顺序和逻辑。
    • 实际应用中常常省略,由客户端直接调用 Builder。

4.3 具体实现

比如我们现在要自定义一个http发送请求连接:

传统的方式(构造函数)

http对象代码为:

java 复制代码
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;
    }

    @Override
    public String toString() {
        return "HttpRequest { " +
               "url='" + url + '\'' +
               ", method='" + method + '\'' +
               ", headers=" + headers +
               ", body='" + body + '\'' +
               " }";
    }
}

构建方式:

java 复制代码
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对象代码:

java 复制代码
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;
    }

    @Override
    public String toString() {
        return "HttpRequest { " +
               "url='" + url + '\'' +
               ", method='" + method + '\'' +
               ", headers=" + headers +
               ", body='" + body + '\'' +
               " }";
    }
}

构建方式:

java 复制代码
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:

  1. PRODUCT
java 复制代码
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;
    }

    // Getters
    public String getUrl() { return url; }
    public String getMethod() { return method; }
    public Map<String, String> getHeaders() { return headers; }
    public String getBody() { return body; }
}
  1. 构建器类(Builder)
java 复制代码
public interface Builder {
    Builder setUrl(String url);
    Builder setMethod(String method);
    Builder addHeader(String key, String value);
    Builder setBody(String body);
    HttpRequest build();
}
  1. 具体构建器(ConcreteBuilder)
java 复制代码
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;

    @Override
    public Builder setUrl(String url) {
        this.url = url;
        return this;
    }

    @Override
    public Builder setMethod(String method) {
        this.method = method;
        return this;
    }

    @Override
    public Builder addHeader(String key, String value) {
        this.headers.put(key, value);
        return this;
    }

    @Override
    public Builder setBody(String body) {
        this.body = body;
        return this;
    }

    @Override
    public HttpRequest build() {
        // 校验必填参数
        if (url == null || method == null) {
            throw new IllegalStateException("URL and Method are required!");
        }
        return new HttpRequest(url, method, headers, body);
    }
}
  1. 指导者类(Director,可选)
java 复制代码
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();
    }
}
  1. 客户端代码
java 复制代码
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 的优势

  1. 强制性:
    • 必须设置关键参数(如 URL、method),否则无法创建对象。
  2. 校验性:
    • build() 方法集中校验,确保对象状态一致。
  3. 可读性:
    • 链式调用使代码更直观,避免参数顺序错误。

通过这种方式,Builder 模式能够显著提升代码的安全性和可靠性,避免遗漏关键参数的问题。

4.4 使用场景

  1. 参数多且部分可选的对象构建
    • 如配置类、网络请求对象(如 oKHttp)、数据库连接对象等。
  2. 不可变对象的构建
    • 需要在对象创建后禁止修改时,如 StringBuilderImmutable 对象。
  3. 复杂初始化逻辑的对象
    • 如需要组合多个子对象或处理复杂逻辑的初始化。
  4. 跨平台对象创建
    • 如需要动态生成 JSON、XML 等结构。
  5. 避免构造器参数过多
    • 如果构造器参数多且顺序容易搞混,Builder 模式可以显著改善代码可读性和安全性。
相关推荐
信号处理学渣6 分钟前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客7 分钟前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
jasmine s16 分钟前
Pandas
开发语言·python
装不满的克莱因瓶29 分钟前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗36 分钟前
常用类晨考day15
java
biomooc37 分钟前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人39 分钟前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
black^sugar41 分钟前
纯前端实现更新检测
开发语言·前端·javascript
404NooFound1 小时前
Python轻量级NoSQL数据库TinyDB
开发语言·python·nosql
用余生去守护2 小时前
python报错系列(16)--pyinstaller ????????
开发语言·python