一、请介绍简单工厂与工厂方法的区别
工厂模式是设计模式的一个大类,工厂模式包括三种:
- 简单工厂
- 工厂方法
- 抽象工厂
前两种最常用。
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;
}
}
好处:不修改原始代码
Comments | NOTHING