Java设计模式学习

Java设计模式学习

Auther: Ice Man

StartTime:2022-2-15

Book:Head First 设计模式

前言:编程中会出现的问题

继承

问题: 如果项目中众多子类来继承父类的特性,考虑到有些子类并不需要去复用父类的一些特性,或者父类增加某些特性时,可能会造成某些子类功能的混乱(例如🦆和橡皮鸭的区别)🤔🤔🌚🌚

尝试解决?

采用接口

接口可以使每个子类去掉用自己需要的特性,避免了子类全部继承父类的特性,但当我们去增加新的子类时,需要去考虑每一个接口是否需要调用,维护项目的时候也大幅增加了工作量,并不是一个很好的方法👎

思考

🌟设计原则🌟:找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要的变化的代码混在一起。

把会变化的部分取出并 “封装” 起来 => 代码变化减少,系统变得有弹性

解决方法

依然用鸭子🦆这个类来举例,我们在设计类的时候,将会飞行和回呱呱叫的鸭子用两个不同的类来描述

🌟设计原则🌟:针对接口编程,而不是针对实现编程

接口实现方法

策略模式(Strategy Pattern)

思考:”有一个“ 和 ”是一个“ 之间的区别?😎😎😎

问题:”有一个“ 和 ”是一个“ 之间的区别?😎😎😎

🌟设计模式🌟:多用组合,少用接口

策略模式 定义了算法组,分别封装起来,让他们之间可以互相转换,此模式让算法的变化独立于使用算法的客户

场景:在设计一款复杂的生存游戏的时候,我们有很多函数需要封装,例如仅仅是开枪这一个动作,我们就可以拆分成选择武器装填武器瞄准目标开枪等等多个动作,这样去将一个复杂的实现过程进行拆分,不仅能提高函数的复用性,还能节省后期维护成本

总结
  1. 仅仅知道OO(面向对象)基础,并不能设计出良好的OO系统
  2. 良好的OO设计必须具备可复用可扩充可维护三个特性
  3. 模式可以让我们建造出具有良好OO设计质量的系统
  4. 模式让开发人员之间有共享的语言,能够最大化沟通的价值
  5. 大多数的模式和原则,都着眼于软件变化的主题

二、观察者模式(Observer)

场景

帮助一个气象公司设计一个系统用来显示当前追踪到的天气状况(温度、湿度、气压),要求所有的数据能够进行实时更新

项目梗概图

在这里插入图片描述
需求分析

我们的工作就是建立一个应用,利用设计的系统(WeatherData对象)取得数据,并更新三个公告板:目前状况、气象统计和天气预报

分析已有资源

气象站提供的代码如下所示
在这里插入图片描述

前三个数据很明显用于获取实时的气象测量数据,Changed方法是用来检测气象数据,一旦气象测量数据更新,就会调用该方法

/*
 * 一旦气象测量更新,此方法就会被调用
 */
public void measurementsChanged() {
  // 实现代码
}
需求总结
  1. WeatherData类具有getter方法可以取得三个测量值:温度、湿度和气压
  2. 当新的测量数据备妥时,measurementsChanged( )方法就会被调用(不用纠结是如何被调用的)
  3. 我们需要三个实用天气数据的布告板:“目前状况“布告、“气象统计”布告 和 “天气预告”布告。一旦WeatherData有新的测量,这些布告必须马上更新
  4. 此系统必须可扩展,让其他开发人员建立定制的布告板,用户可以随性所欲地添加或删除任何布告板。目前初始的布告板有以上三类
错误示范

以下代码有何问题

public class WeatherData {
  public void measurementsChanged() {
    // 实例变量申明
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();

    currentConditionDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure);
  }
 
  // 这里是其他WeatherData方法
}

出现问题:

  1. 我们是针对具体编程,而非针对接口
  2. 对于每一个新的布告板,我们都得修改代码
  3. 我们无法在运行时动态地增加或删除布告板
  4. 我们尚未封装改变的部分
观察者的一天

在这里插入图片描述

以上面模型为为例子,主题对象主要管理某些数据,当主题内的数据改变,就会通知观察者

一旦数据改变,新的数据会以某种形式送到观察者手上

狗,猫,老鼠对象都为观察者,可以在主题数据改变时能够收到更新,而鸭子对象不属于观察者对象,所以主题数据改变时不会被通知

观察者模式👀 = 出版者 + 订阅者

1. 订阅主题对象
  1. 鸭子对象过来告诉主题对象,他想当一个观察者(向主题对象注册【订阅】)
  2. 鸭子对象现在已经是正式的观察者了
  3. 当主题对象的数据进行更新,鸭子和其他对象就都会接收到通知:主题已经改变了

在这里插入图片描述

2. 退订主题对象
  1. 老鼠对象要求从观察者中把自己除名
  2. 主题知道老鼠的请求后,把它从观察者中除名了(老鼠离开了!)
  3. 主题现在有一个新的整数,出了老鼠之外,每个观察者都会收到新的通知[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    在这里插入图片描述
观察者模式的定义

观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

实现观察者模式的方法不只一种,但是以包含Subject与Observer接口的类设计的做法最常见

类图
在这里插入图片描述

  1. 主题接口: 对象使用此接口注册为观察者,或者把自己从观察者中删除

  2. 观察者接口:所有潜在的观察者必须实现观察者必须实现观察者接口,这个接口只有update( )一个方法,当这个主题状态改变时它该调用

  3. 具体主题的实现方法:具体主题里除了注册和撤销方法之外,具体主题还实现了notifyObserver( )方法,此方法用于在状态改变时更新所有当前观察者

    具体主题也可能有设置和获取状态的方法

  4. 观察者的实现方法:可以是实现该接口的任意类,观察者必须注册具体主题,以便接收更新

松耦合

当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节

🌟设计原则🌟: 为了交互对象之间的松耦合设计而努力

任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随意增加观察者。事实上,在运行时我们可以用新的观察者来取代现有的观察者,主题不会收到任何影响。同样的,也可以在任何时候删除某些观察者

有新类型的观察者出现时,主题的代码不需要修改。如果我们有个新的具体类需要当观察者,我们不需要为兼容新类型而修改主题代码的代码,所有要做的就是在新的类里实现此观察者接口,然后注册成为观察者即可。主题不在乎别的,他只会发送通知给所有实现了观察者接口的对象

我们可以独立复用主题或者观察者。如果我们在其他地方需要使用主题或者观察者,可以轻易复用,应为二者并为紧耦合

改变主题或观察者的任何一方,并不会影响另一方。因为二者是松耦合,所以只要他们之间的接口仍被遵循,我们就可以自由地改变他们

松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低

f3d3f3bbee8ec6ba83452d2838f5b9ed

实现项目需求

思考一:现在让我们回到气象站项目,那我们该如何去用观察者模式来实现功能?

分析模式:观察者模式定义了对象之间的**“一对多”**的依赖,而在这个项目中WeatherData类正是“一”,“多”就是使用天气观测的各种布告板

思考二:解决了模型应用场景后,那怎么将气象预测值放到布告板上?

分析模式:观察者模式中有两个对象,主题对象和观察者对象,如果将WeatherData对象作为主题,把布布告板作为观察者,布告板为了取得信息,就必须先向WeatherData对象注册

思考三:每一个布告板都会去展示不一样的数据,怎么做到代码的通用性?

分析模式:这正是我们需要一个共同的接口的原因,尽管布告板的类都尽不相同,但是它们都应该实现相同的接口,好让WeatherData对象能够知道如何把观测值送给他们,所以有一个update( )方法在所有的布告板都实现的共同接口里定义

设计气象站

设计图如下

在这里插入图片描述

实现气象站

Java为观察者模式提供了许多内置的支持,这里先不使用,而是先自己从头开始写首先从创建接口开始

实例代码: http://gitlab.code-nav.cn/13706531210/observerpattern.git

1. 创建接口
// 主题接口
public interface Subject {
  public void registerObserver(Observer o);		// 注册观察者
  public void removeObserver(Observer o);			// 移除观察者
  public void notifyObserver();						// 当主题状态发生改变时,这个方法会被调用,来通知所有观察者
}

// 观察者接口
public interface Observer {
  public void update(float temp, float humidity, float pressure);		// 传递气象状态值给观察者
}

// 布告板展示接口
public interface DisplayElement {
  public void display();		// 当布告板需要展示时,调用此方法
}

思考点:直接把观测值传入观察者这个方法是否明智?(暗示:这些观测值的种类和个数在未来有可能改变,改变后是否能够很好地封装,或者是否需要修改许多代码才能办到?)

在实现基础方法后,会根据这个问题进行优化

2. 在WeatherData中实现主题接口
import interfaces.Observer;
import interfaces.Subject;

import java.util.ArrayList;

public class WeatherData implements Subject {
    private ArrayList observers;    // 注册的观察者
    private float temperature;      // 温度
    private float humidity;         // 湿度
    private float pressure;         // 气压

    public WeatherData() {
        observers = new ArrayList<>();
    }

    /**
     * 注册观察者
     */
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    /**
     * 移除观察者
     */
    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(0);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    /**
     * 将状态告诉每一个观察者
     */
    @Override
    public void notifyObserver() {
        for (int i = 0; i < observers.size(); i++) {
            Observer observer = (Observer) observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    /**
     * 当从气象站得到更新观测值时,我们通知观察者
     */
    public void measurementsChanged() {
        notifyObserver();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
    }

    // WeatherData的其他方法
}
3. 创建布告板

布告板的逻辑都十分类似,这里就先实现其中一种,其他的布告板可以自己去定义编写

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private Subject weatherData;

    /**
     * 注册观察者
     */
    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        // 把最近的温度和湿度保存并调用display
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    /**
     * 把最近的温度和湿度显示出来
     */
    @Override
    public void display() {
        System.out.println("Current conditions: " + temperature 
                + "F degrees and " + humidity + "% humidity");
    }
}
4. 启动气象站

现在先建立一个测试程序

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

        // 模拟新的气象数据
        weatherData.setMeasurements(80, 65, 30.4f);
        currentDisplay.display();
        weatherData.setMeasurements(82, 70, 29.2f);
        currentDisplay.display();
        weatherData.setMeasurements(78, 90, 29.2f);
        currentDisplay.display();
    }
}

展示程序运行结果
在这里插入图片描述

模式思考

思考一:为什么需要主题去发送更新信息,而不是让每个观察者去索取数据?🤔️

😎如果采用观察者去主动索取数据,就意味着主题类需要门户大开,这样主题就暴露在所有观察者面前了,这样十分危险

思考二:主题为什么不开放一些getter方法让观察者去获取它们所需要的数据

😎通过主题直接推送信息可以减少观察者的调用次数,但每个观察者也并不是需要主题里所有的数据,get方法可以避免观察者获取不需要的信息

😎直接推送所有消息和观察者get部分数据两种方法各有优点,Java内置的Observer模式两种方法都支持

使用Java内置的观察者模式

上面通过自己写的观察者模式的代码还是比较繁琐,Java Util包中提供了最基本的Observer接口与Observable类,里面许多功能都已经实现准备好了,可以使用推(push)和拉(pull)的方式传输数据。通过Java内置的观察者,我们可以将结构图进行简化
在这里插入图片描述

1. 如何把对象变成观察者

如同以前一样,实现观察者接口(java.util.Observer),然后调用任何Observable对象的addObserver( )方法。不想再当观察者时,调用deleteObserver( )方法就可以了

2. 观察者如何送出通知
  1. 先利用java.util.Observable接口产生 “可观察者“ 类
  2. 调用setChanged( )方法,标记状态已经改变的事实
  3. 调用两种notifyObservers( )方法中的一个:notifyObserversnotifyObservers(Object arg)
3. 观察者如何接受通知

同以前一样,观察者实现了更新的方法,但是方法的签名不太一样

update(Observable o, Object org)

如果你想 ”推“(pull)数据给观察者,你可以把数据当作数据对象传送给notifyObservers(arg)方法。否则观察者就必须从可观察者对象中“拉”(pull)数据。

4. setChanged()方法

Java内置的观察者比我们之前编写的代码多了setChanged( )方法,现在单独把setChanged方法拉出,以来了解这一切

setChanged() {
  changed = true;
}

notifyObservers(Object arg) {
  if (changed) {
    for every observer on the list {
      call update(this, arg)
    }
    changed = false;
  }
}

public void notifyObservers() {
	notifyObservers(null);
}
利于Java内置的支持重做气象站
1. 把WeatherData改成使用java.util.Observable
public class WeatherDataByJavaUtil extends Observable {
    private float temperature;      // 温度
    private float humidity;         // 湿度
    private float pressure;         // 气压

    public WeatherDataByJavaUtil() { }      // 构造器不再需要为了记住观察者们而建立数据结构了

    public void measurementsChanged() {
        // 我们没有调用notifyObservers()传送数据对象,所以这里需要设置一下changed值
        setChanged();
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

以上中的三个get方法并不是新方法,只是因为要使用 ”拉“,而特意再写一遍来强调。观察者会利用这些方法取得WeatherData对象的状态

⚠️注意:这里没有调用notifyObservers( )来传送数据,说明是由观察者对象向主题“拉”数据

2. 重写CurrentConditionsDisplay
public class CurrentConditionsDisplay implements Observer, DisplayElement {

    Observable observable;
    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions: " + temperature
                + "F degrees and " + humidity + "% humidity");
    }

    @Override
    public void update(Observable obs, Object arg) {
        if (obs instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
}

构造器中需要一个Observable当参数,并将当前CurrentConditionsDisplay对象登记成为观察者

运行后发现运行结果与之前自己写的如出一辙
在这里插入图片描述

Observable的缺陷
  1. Java自带的Observable并不是一个接口,而是一个类,这似乎违反了我们OO设计原则中的针对接口编程,更糟糕的是Observable类中甚至没有一个实现接口。因此java.util.Observable的实现有许多问题,限制了它的使用和复用,但这并没有影响它应有的功能
  2. 从Observable是一个类出发,我们又能引出两个问题
    1. Observable是一个类,你必须设计一个类去继承它。但如果某个类相同时具有Observable类和其他另一个超类的行为,就会陷入两难,限制了Observable的复用潜力
    2. 因为没有Observable接口,所以无法建立自己的实现,和Java内置的Observer API搭配使用,也无法将java.util的实现换成另一套方法实现
  3. Observable中将setChanged方法保护起来了,这意味着除非去继承Observable,否则无法创建Observable实例并组合到自己的对象中来。这违反了设计原则第二点:多用组合,少用继承
总结
  1. 观察者模式定义了对象之间一对多的关系
  2. 主题(也就是可观察者)用一个共同的接口来更新观察者
  3. 观察者和可观察者之间用松耦合方式结合
  4. 使用观察者模式,你可以从被观察者处推(push)或者拉(pull)的方式获取数据(建议使用推的形式)
  5. 有多个观察者时,不可以依赖特定的通知次序
  6. Java有多种观察者模式的实现,包括通用的java.util.Observable
  7. 注意Observable类上的缺陷
  8. 如果有必要的话,建议实现自己的Observable
  9. 此模式应用于许多地方,例如:JavaBeans、RMI

三、装饰者模式

场景

星巴兹(Starbuzz)是一家以扩张速度最快而闻名的咖啡连锁店,因为吸纳在

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ice Programmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值