29.4 设计模式 必知必会



一、请介绍简单工厂与工厂方法的区别

工厂模式是设计模式的一个大类,工厂模式包括三种:

  • 简单工厂
  • 工厂方法
  • 抽象工厂

前两种最常用。

1.1 区别

1.2 简单工厂

以之前的工程factory为例:

由上可知:

特点:所有产品的创建,只在一个工厂类中进行;

好处:可以让客户端,与提供服务的工程师,有效解耦。

客户端不在乎具体的底层实现,只需要直接使用工厂模式提供的方法,就能直接获得自己想要的产品。

工业时代的产品:

用户只需要会用工业产品即可,没必要理解产品背后的每一个细节。

比如,老百姓只需要会用手机即可,没必要去理解手机是如何生产的

不足:如果要新增一种语言,即要新增一种产品,必须修改简单工厂的代码,增加额外的if-else分支。

这违背了“开闭原则”:对扩展开放,对修改关闭。说人话,就是对软件增加新功能新特性时,应该对软件进行扩展,而不能直接修改它的原代码。

只有一个大工厂:既要生产饼干,又要生产罐头,还要生产牛奶。

i18n(其来源是英文单词internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。

在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。

对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。

1.3 工厂方法

以之前的工程factory为例:

由上可知:

特点:为每一种具体的产品,提供了对应的工厂类。几个工厂类有一个顶级的抽象接口。

(你不同的产品可以抽象出一个接口,那么我工厂也可以分为几个分厂,也抽象出一个接口啊:))

好处:解决了简单工厂的扩展性不足的问题。

​ 比如:新增了一个墨西哥语,只要新增一个墨西哥语的语言对象,再对应的去创建一个墨西哥语的工厂,这样客户端访问的时候,直接 去实例化墨西哥工厂,就可以得到对应的对象了。不用修改原代码。

适用场景:适合产品类型不是特别多的情况。因为如果有1000中语言类,那么就要创办1000个工厂类。

为什么要加一层工厂呢,直接在客户端用户使用时,直接new对应的西班牙语的语言类,不就完了?

因为后续如果西班牙语的语言类,设计太过陈旧,有了新的替代者,只需要在工厂里更换,在客户端的调用时,是啥都不用管、无感知的。

工厂方法;

把大工厂拆分成若个小工厂:专门生产饼干的饼干工厂,专门生产罐头的罐头工厂,专门生产牛奶的牛奶工厂。什么?客户还需要火腿肠?好,那就再单独创建一个工厂,专门来生产火腿肠!~

1.4 简单工厂+反射技术(XML配置文件)

虽然简单工厂有不足,工厂方法弥补了不足,但是,平时使用最多的仍是简单工厂。

因为可以有其他的途径,来弥补简单工厂的扩展性不足的缺点。

简单工厂+反射技术(XML配置文件):

也正是Spring IOC的核心原理,从而能够实现对象的创建、管理工作。

二、请介绍观察者模式以及它的使用场景

观察者模式,工作中使用较多,相对复杂。

2.1 观察者模式

定义:就是对目标对象进行观察、监听,当目标对象的状态发生变化时,会自动的触发后续操作。

使用场景:比如前端的界面开发:鼠标单击事件

2.2 观察者模式的类图

类图(class diagram)

显示模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等.

用于面向对象建模

由类图可知:

观察者模式主要有三部分:

2.3 代码示例:病人、监听器

1.

现实案例:

医院的ICU病房:

会给病人绑定各种各样的监听设备:比如血压监听器、心跳监听器等。---------------- 观察者

病床上的病人本身。----------------------------------------------------------------------------------- 目标对象

2.

以老师提供的素材中,的工程observer为例:

在类Patient中:

/**
 * 病人,即Subject目标对象
 */
public class Patient {
    //在Subject内部只有Observer观察者对象集合
    private List<Monitor> monitors = new ArrayList<Monitor>();
    private String name = "";  //姓名
    private Integer low = 0;   //低压
    private Integer high = 0;  //高压
    private Integer heartbeat = 0; //心跳

    //构造方法,设置病人姓名
    public Patient(String name){
        this.name = name;
    }

    //为病人绑定监视器
    public void attach(Monitor monitor){
        monitors.add(monitor);
    }

    //设置病人当前状态数据,低压、高压、心跳
    public void setState(Integer low , Integer high,Integer heartbeat){
        this.low = low;
        this.high = high;
        this.heartbeat = heartbeat;
        //当病人状态发生改变时,通知所有监视器更新数据                // 一对多的关系
        notifyMonitors();
    }

    //同时所有监视器更新数据
    public void notifyMonitors(){
        for (Monitor monitor : monitors) {
            monitor.update();
        }
    }


    public Integer getLow() {
        return low;
    }


    public Integer getHigh() {
        return high;
    }


    public Integer getHeartbeat() {
        return heartbeat;
    }

    public String getName() {
        return name;
    }


}

在抽象类Monitor中:

//Observer观察者类
public abstract class Monitor {

    //在Observer对象内部,要持有Subject对象(病人)
    protected Patient patient;

    //在构造方法中要求传入Subject对象
    public Monitor(Patient patient){
        this.patient = patient;
    }

    //update代表数据更新,不同的观察者update执行逻辑是不同的
    public abstract void update();


}

在上述抽象类的实现类BloodPressureMonitor中:

表示血压监听器:

//具体的Observer类,当前Observer用于对病人血压进行警报
public class BloodPressureMonitor extends Monitor{

    //构造方法,实例化时必须传入病人对象
    public BloodPressureMonitor(Patient patient) {
        super(patient);
    }

    //update方法用于对病人血压进行监控,超出正常值则立即警报
    @Override
    public void update() {
        if(patient.getLow() < 60 || patient.getLow() > 90 ){
            System.out.println(patient.getName() + "病人低压异常:" + patient.getLow() + ",请速通知医生进行检查");
        }

        if(patient.getHigh() < 90 || patient.getHigh() > 140){
            System.out.println(patient.getName() + "病人高压异常:" + patient.getHigh() + ",请速通知医生进行检查");
        }
    }
}

在上述抽象类的实现类HeartbeatMonitor中:

表示心跳监听器

//具体的Observer类,当前Observer用于对病人心跳进行警报
public class HeartbeatMonitor extends Monitor{

    //实例化时必须传入病人对象
    public HeartbeatMonitor(Patient patient) {
        super(patient);
    }

    @Override
    //update方法用于对病人心跳进行监控,超出正常值则立即警报
    public void update() {
        if(patient.getHeartbeat() < 60 || patient.getHeartbeat() > 100 ){
            System.out.println(patient.getName() + "病人心跳异常:" + patient.getHeartbeat() + ",请速通知医生进行检查");
        }
    }

}

在应用的入口类中:

针对张三这个病人,绑定两种监听器:

public class Application {

    public static void main(String[] args) {
        //初始化
        Patient patient = new Patient("张三");
        patient.attach(new BloodPressureMonitor(patient));
        patient.attach(new HeartbeatMonitor(patient));

        //模拟病人正常情况
        System.out.println("第一次模拟正常情况");
        patient.setState(80,120,90);
        try {
            Thread.sleep(5000);
            System.out.println("=======================");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //模拟病人低压异常情况
        System.out.println("第二次模拟病人低压异常情况");
        patient.setState(50,120,90);
        try {
            Thread.sleep(5000);
            System.out.println("=======================");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //模拟病人所有指标异常情况
        System.out.println("第三次模拟病人所有指标异常情况");
        patient.setState(100,180,150);
    }
    
}

执行,结果为:

第一次模拟正常情况
=======================
第二次模拟病人低压异常情况
张三病人低压异常:50,请速通知医生进行检查
=======================
第三次模拟病人所有指标异常情况
张三病人低压异常:100,请速通知医生进行检查
张三病人高压异常:180,请速通知医生进行检查
张三病人心跳异常:150,请速通知医生进行检查

由上可知:

观察者模式的好处:把具体的观察者,与目标对象进行了解耦。因为在客户端调用监听器的时候,这些组合是可以灵活配置的。

比如,针对张三,是绑定的血压和心跳的监听器,针对李四,只绑定心跳监听器即可,或者以后会根据危重病人,增加更多的监听设备。

三、请介绍静态代理与动态代理的区别

静态代理与动态代理,就是代理模式的不同实现。

3.1 代理模式

本质:

将客户类与提供服务的服务类,进行解耦;

然后,通过代理类,实现代理、功能扩展。

比如:

原先:

你要租房子,你直接跟房东对接;

现在:

你跟第三方的中介对接,因为中介是房东的代理人

静态代理:必须需要Java工程师,手动的创建代理类。

动态代理:利用Java的反射技术,或CGLib技术,来动态的自动的生成代理类。

3.2 代码示例:静态代理:手动创建代理类,实现与目标类相同的接口

以之前学习AOP时的工程s04为例:

接口UserService

public interface UserService {
    public void createUser();
}

继承上述接口的,实现类UserServiceImpl

// 房东
public class UserServiceImpl implements UserService {

    @Override
    public void createUser() {
        System.out.println("执行创建用户的业务逻辑");
    }
    
}

代理类UserServiceProxy

为了保证代理类能对房东进行代理、扩展,代理类也必须跟房东一样,继承同样的接口,并且持有房东作为其属性之一

用于扩展前置功能

// 静态代理:必须需要Java工程师,手动的创建代理类
// 代理类:中介
public class UserServiceProxy implements UserService {

    // 核心特点:持有委托类的对象
    private UserService userService;


    // 定义带参的构造方法
    public UserServiceProxy(UserService userService) {  // 该参数userService是代理类实例化时,从外侧传入进来的
        this.userService = userService;                 // 赋值
    }


    @Override
    public void createUser() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String now = sdf.format(new Date());
        System.out.println("===========" + now +"==========");       // 以上是前置的扩展功能

        userService.createUser();                                    // 原有的共同职责
    }
}

代理类的代理类UserServiceProxy1

用于拓展后置功能

// 感觉就是上面中介的中介  :)
public class UserServiceProxy1 implements UserService {

    private UserService userService;
                                                            // 多级代理的特性:因为这里的userService是接口,既可以是目标对象(服务类,即原房东),也可以是其他的代理类
    public UserServiceProxy1(UserService userService) {     // 该参数userService是代理类实例化时,从外侧传入进来的
        this.userService = userService;                     // 赋值
    }

    @Override
    public void createUser() {
        userService.createUser();                           // 原先的功能
        System.out.println("==========后置扩展功能============");
    }
}

在应用入口类中:

public class Application {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();        // 1.小明直接面对房东
        userService.createUser();

        UserService userService = new UserServiceProxy(new UserServiceImpl());   // 2.小明面对中介
        userService.createUser();

        // 不管你是什么类的对象,只要你实现了UserService接口,就可以传入其中
        UserService userService = new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));   // 3.小明直接面对中介的中介
        userService.createUser();

    }
}

由上可知:

好处:提供了前置和后置的功能扩展

唯一不足:不灵活。因为有一个目标类(服务类),就必须创建至少一个代理类。如果系统的目标类(服务类)有1000个呢?

怎么办呢?

动态代理:利用Java的反射技术,或CGLib技术,来动态的自动的生成代理类

3.3 代码示例:动态代理:JDK,反射技术

JDK动态代理类

以之前学习AOP时的工程s05为例:

接口UserService

public interface UserService {
    public void createUser();
}

继承上述接口的,实现类UserServiceImpl

public class UserServiceImpl implements UserService {

    @Override
    public void createUser() {
        System.out.println("执行创建用户的业务逻辑");
    }
}

接口EmployeeService

public interface EmployeeService {

    public void createEmployee();
}

继承上述接口的,实现类EmployeeServiceImpl

public class EmployeeServiceImpl implements EmployeeService {

    @Override
    public void createEmployee() {
        System.out.println("执行创建员工的业务逻辑");
    }
}

动态代理类ProxyInvocationHandler

/**
 * InvocationHandler 是JDK提供的反射接口,用于在JDK动态代理中,对目标方法进行增强
 * 丛使用角度,跟AOP切面类的环绕通知方法很像
 */
public class ProxyInvocationHandler implements InvocationHandler {      // InvocationHandler:是反射包下的接口

    // 作为代理类,必须持有目标类的实例对象
    private Object target;                                    // 因为本类面向所有类生效,所以使用父类Object

    // 构造方法中,传入对应的目标对象
    private ProxyInvocationHandler(Object target) {
        this.target = target;
    }


    /**
     * invoke方法,就是对目标方法增强的部分
     * @param proxy 代理类的实例对象
     * @param method 要执行的目标方法的实例对象
     * @param args 目标方法中传入的参数
     * @return 目标方法运行后的返回值
     * @throws Throwable 目标方法抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String now = sdf.format(new Date());
        System.out.println("===========" + now +"==========");          // 扩展功能:时间的输出

        Object ret = method.invoke(target, args);       // 调用目标方法。相当于AOP环绕通知中的ProceedingJoinPoint.proceed()方法
        return ret;
    }




    public static void main(String[] args) {
        // 动态代理UserService
        UserService userService = new UserServiceImpl();
        ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
        // 动态创建代理类:基于接口,创建指定的代理类------运行时,自动的动态完成
        // 参数:类加载器,类要实现的接口,对目标方法的扩展
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
        // 基于代理,进行调用
        userServiceProxy.createUser();

        
       // 也可动态代理EmployeeService
        // 动态代理,只有实现接口才可以运行
        EmployeeService employeeService = new EmployeeServiceImpl();
        EmployeeService employeeServiceProxy = (EmployeeService) Proxy.newProxyInstance(employeeService.getClass().getClassLoader(), employeeService.getClass().getInterfaces(), new ProxyInvocationHandler(employeeService));
        
        employeeServiceProxy.createEmployee();
    }
}

好处:全局只需要创建一个代理类即可,不用像静态代理一样,为每一个目标服务类,就要单独创建一个;

不足:目标服务类(房东)必须有接口才可以用。Spring开发时,不能保证所有类都有接口啊。那怎么办呢?

3.4 代码示例:动态代理:CGLib技术,继承

Spring AOP中,如果目标服务类(房东)没有使用接口,那么就会默认使用CGLib技术,进行代理和扩展;

代码示例:

脑海中有这个图,用自己的白话说出来即可

四、请说明适配器模式的作用

4.1 适配器模式

4.2 代码示例

1.

假设服务的提供者,是由团队A来开发的。

服务的提供者,与服务的调用者,约定:数据处理都是基于JSON。

2.

服务的提供者,是由团队A不能再使用JsonDataSource,而是使用XmlDataSource

3.

团队核心、高级工程师、老鸟:

额外创建一个适配器类JsonAdapter

用于完成上述XML文本向JSON的转换工作:

其中

package com.imooc.adapter.consumer;
import com.imooc.adapter.provider.DataSource;


//适配器典型特征
//1.实现与目标类相同的接口
public class JsonAdapter implements DataSource {
    //2.通过构造方法内部,持有目标对象
    private DataSource dataSource;

    public JsonAdapter(DataSource dataSource){
        this.dataSource = dataSource;
    }

    //3.重写接口方法,完成接口转换的业务逻辑
    @Override
    public String searchUser() {
        String xml = dataSource.searchUser();
        //XML转换为JSON的代码省略
        String json = "{\"username:root\",\"password:123456\"}";
        return json;
    }
}

好处:不修改原始代码

声明:Jerry's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 29.4 设计模式 必知必会


Follow excellence, and success will chase you.