GOF23
一种思维,一种态度,一种进步
工厂模式
作用:
实现了创建者和调用者的分离
核心本质:
三种形式的工厂模式
不使用工厂模式的时候
简单工厂的使用
方法一:
方法二:
思路图:
抽象工厂的使用
优点:
具体产品在应用层的代码隔离,无需关系创建的细节
将一个系列的产品统一到一起创建
缺点:
规定了所有可能被创建的产品集合,产品族中扩展新的产品困难
增加了系统的抽象性和理解难度
总结:
静态代理
角色分析:
抽象角色
:一般会使用接口或者抽象类解决真实角色
:被代理的角色代理角色
:代理真实角色,代理真实角色后,我们一般会做一些附属操作客户
:访问代理对象的人初始模式模拟:
代理模式模拟:
代理模式模拟功效:
思路图:
代理模式的好处:
缺点:
动态代理
动态代理的类别
基于接口的代理(JDK)
基于类的动态代理(CGLIB)
静、动态代理(jdk,cglib)的异同
1.静态代理简单,其代理模式是动态代理的理论基础
2.jdk动态代理,需要有顶层接口才能使用,且在只有顶层接口的时候也可以使用,常见的有Mybatis
的mapper文件是代理,使用反射完成的,使用了动态生成字节码技术
3.cglib动态代理,可以直接代理类,使用字节码技术,不能对final类进行继承。使用了动态生成字节码
技术
4.动态代理的代理类是动态生成的,不是开发者手动写好的
动态代理的目的
为了在业务中给需要实现的方法添加预处理或添加后续操作,但是不干预实现类的正常业务,把一些基本
业务和主要的业务逻辑分离,我们所熟知的spring的aop即为动态代理实现的
动态代理的核心
Java动态代理机制中,基于jdk的动态代理需要知道两个重要的类和接口,Proxy(类)和
InvocationHandler(接口),通过两者实现动态代理。
值得一提的是,InvocationHandler在spring框架中被广泛使用,这意味着理解了InvocationHandler
将为spring源码的学习打下坚实的基础。
使用步骤
1.创建一个接口
2.实现接口
3.创建实现了接口的类的代理类
简单讲解
-----------------------------------------------------------
proxyInvocationHandler -->调用处理程序
Proxy -->代理实例
InvocationHandler -->调用处理程序要实现的一个接口
-----------------------------------------------------------
1.InvocationHandler接口是Proxy代理实例的调用处理程序实现的一个接口
2.每一个proxy代理实例都有一个关联的调用处理程序
3.在代理实例调用方法时,方法调用,且被编码分派到调用处理的程序的invoke方法
-----------------------------------------------------------
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联
到了实现该接口的动态代理类调用处理程序中,当开发者通过动态代理对象调用一个方法时候,这个方法
的调用就会被转发到实现InvovationHandler接口类的Invoke方法来调用,看如下invoke方法:
public Object invoke(Object proxy,Method method ,Object[] args) throws Throwable{
}
1.proxy 代理类代理的真实代理对象com.sun.proxy.$Proxy()
2.method 我们所要调用某个对象真实的方法的Method对象
3.args 指代理对象方法传递的参数
解析proxy
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是开发者最常用的是
newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这个方法的作用就是创建一个代理类对象,它接收三个参数
1.loader
一个classloader对象,定义了由哪个classloader对象对生成的代理类
进行加载
2.Interfaces
一个interface对象数组,表示开发者将要给代理对象提供一组什么样的接口
,如果提供了一个接口对象数组,那么也就是声明了代理类实现了这些接口,
代理类就可以调用接口中声明的所有方法
3.h
一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会
关联哪一个InvocationHandler对象上,并最终由其调用
建造者模式
含义
建造者属于创建型模式,它提供了一种创建对象的最佳方式。
建造者模式能够将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
换句话说,就是调用相同的创建对象的方法(建造过程)可以创建出不同的对象。
举例
我要创建一部手机,我需要先有手机的几个核心部件,例如:屏幕、电池、听筒、话筒、机身等。
public class MobilePhone {
//手机屏幕
private String screen;
//电池
private String battery;
//话筒
private String microphone;
//听筒
private String phoneReceiver;
//机身
private String phoneBody;
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getBattery() {
return battery;
}
public void setBattery(String battery) {
this.battery = battery;
}
public String getMicrophone() {
return microphone;
}
public void setMicrophone(String microphone) {
this.microphone = microphone;
}
public String getPhoneReceiver() {
return phoneReceiver;
}
public void setPhoneReceiver(String phoneReceiver) {
this.phoneReceiver = phoneReceiver;
}
public String getPhoneBody() {
return phoneBody;
}
public void setPhoneBody(String phoneBody) {
this.phoneBody = phoneBody;
}
}
每一部手机都是上方这个类的对象,在创建一部手机的时候都要保证这几个核心部件的创建。
所以创建手机是需要一个标准规范的,因为这几个核心部件都可以是不同的型号,
不同的型号的部件制造出来的手机也是不同的,这样就有了下面建造规范的接口。
public interface IBuildPhone {
/**
* 建造手机屏幕
*/
void buildScreen();
/**
* 建造手机电池
*/
void buildBattery();
/**
* 建造手机听筒
*/
void buildMicrophone();
/**
* 建造手机话筒
*/
void buildPhoneReceiver();
/**
* 建造手机机身
*/
void buildPhoneBody();
}
有了规范了,就可以创建手机了,先创建一个iphoneX。
public class IPhoneX implements IBuildPhone {
private MobilePhone mobilePhone;
public IPhoneX(){
mobilePhone = new MobilePhone();
}
/**
* 建造手机屏幕
*/
@Override
public void buildScreen() {
mobilePhone.setScreen("OLED显示屏");
}
/**
* 建造手机电池
*/
@Override
public void buildBattery() {
mobilePhone.setBattery("2700mAh电池容量");
}
/**
* 建造手机听筒
*/
@Override
public void buildMicrophone() {
mobilePhone.setMicrophone("听筒");
}
/**
* 建造手机话筒
*/
@Override
public void buildPhoneReceiver() {
mobilePhone.setPhoneReceiver("话筒");
}
/**
* 建造手机机身
*/
@Override
public void buildPhoneBody() {
mobilePhone.setPhoneBody("iphoneX机身");
}
/**
* 创建手机
* @return
*/
public MobilePhone build(){
return mobilePhone;
}
}
创建手机的工具写好了,下面就可以使用了。
public class Director {
/**
* 建造一部手机
* @param buildPhone
* @return
*/
public MobilePhone createMobilePhone(IBuildPhone buildPhone){
buildPhone.buildBattery();
buildPhone.buildMicrophone();
buildPhone.buildScreen();
buildPhone.buildPhoneReceiver();
buildPhone.buildPhoneBody();
return buildPhone.createMobilePhone();
}
@Test
public void thatTest(){
System.out.println(JSON.toJSONString(createMobilePhone(new IPhoneX())));
}
}
上方关键的方法在createMobilePhone()方法,这个方法接收一个IBuildPhone接口的对象,
所以只要符合这个创建手机规范的对象都可以创建一部手机。
createMobilePhone()方法可以接收new IPhoneX()这样一个对象,也可以接收new IPhone8()、new FindX()等等。
具体使用方法在thatTest()方法中。这个方法的运行结果是:
{"battery":"2700mAh电池容量","microphone":"听筒","phoneBody":"iphoneX机身","phoneReceiver":"话筒","screen":"OLED显示屏"}
上面这个例子的实现过程就使用了我们今天要说的建造者模式,我们来分析一下建造者模式的结构。
在建造者模式的结构图中包含如下4个角色
Builder(抽象建造者):
它(IBuildPhone)为创建一个产品的各个部件指定了标准,规定了要创建复杂对象需要创建哪些部分,
并不直接创建对象的具体部分。
Product(产品角色):
它(MobilePhone)是被建造的复杂对象,包含多个组成部分,
具体建造者创建该产品的内部表示并定义它的装配过程。
Director(指挥者):
指挥者(Director),它复杂安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,
可以在Director的方法中调用建造者对象的部件构造与装配方法,完成建造复杂对象的任务。
客户端一般只需与Director进行交互。
ConcreteBuilder(具体建造者):
它实现了Builder接口(IPhoneX),实现各个部分的具体构造和装配方法,定义并明确它所创建的复杂对象,
也可以提供一个方法返回创建好的复杂产品对象。
建造者模式灵活使用
好了,建造者模式到这里就算是介绍完了,然后说一说我们平时在项目中是怎么使用建造者模式的。
先说一下场景,我们一般在开发的过程中都是需要分层的,MVC这个不一般人都不陌生吧,Model-View-Controller。
(我这里只是举例子不一定真的项目中就这样用)
那我们的数据在每一层的传输过程中如果需要增加或删除些额外的功能怎么实现呢?
例子,如下面一个实体类
public class Person {
private Long id;
private String name;
private int age;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
如果说这个类是一个orm框架需要的实体类,它最好的场景是只被后端的数据操作使用,
但是controller中有一个add方法,这个方法是新增一个人员,add方法接收的参数是一个人员对象,
但是这个对象和上面这个Person得属性有些差别,
例如这个对象里面有请求ip,以及这个对象中没有id这个字段(id在数据库中自增,所以前端不允许传过来id )。
这个时候就不能使用Person类的对象作为add的方法了,需要再创建一个类专门来给Controller使用。
/**
* Controller使用的参数类
*/
public class PersonVO {
private String name;
private int age;
private String address;
//ip地址
private String requestIP;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getRequestIP() {
return requestIP;
}
public void setRequestIP(String requestIP) {
this.requestIP = requestIP;
}
}
参数对象可以创建了, 但是PersonVO的对象是需要转成Person的对象的,这样才能插入到数据库中(数据库的insert方法的参数是Person对象)。这种转换操作其实也简单如下代码:
public Person convert2Person(PersonVO personVO){
Person person = new Person();
person.setName(personVO.getName());
person.setAge(personVO.getAge());
person.setAddress(personVO.getAddress());
return person;
}
但是我们通常是不这么做的,因为如果要转换的这个对象的字段很多那需要写很多次对象调setter方法来进行赋值。
一种方式是直接写一个将所有属性当做参数的构造方法,直接一个一个把属性值传入就可以了,
这种方式最简单暴力。
还有一种方式就是需要包装一下这种方式,把Person改造一下。
public class Person {
private Long id;
private String name;
private int age;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
Person(){}
Person(String name,int age,String address){
this.name = name;
this.age = age;
this.address = address;
}
public static Person builder(){
return new Person();
}
public Person name(String name){
this.name = name;
return this;
}
public Person age(int age) {
this.age = age;
return this;
}
public Person address(String address) {
this.address = address;
return this;
}
public Person build(){
return new Person(name,age,address);
}
}
后面新增了两个构造函数,以及一个builder()方法和一个build()方法,还有几个赋值方法,需要注意的是赋值方法和setter方法的区别,
这样的赋值方法是在赋值后将当前对象返回,用来实现链式调用。
这样在对象转换的时候就可以这样用了:
public Person convert2Person(PersonVO personVO){
return Person.builder()
.name(personVO.getName())
.age(personVO.getAge())
.address(personVO.getAddress())
.build();
}
这种方式其实也是一种建造者模式的应用,这种方式在构建对象的过程实现起来更灵活,例如如果这个对象就只有前两个参数有值,
address是没有内容的,那可以直接这样写:
public Person convert2Person(PersonVO personVO){
return Person.builder()
.name(personVO.getName())
.age(personVO.getAge())
.build();
}
在填充了两个属性后就直接调用build()方法区创建对象。
其实为了实现这种创建对象的方式,每次除了写getter/setter方法后还需要写这么多其他的代码,这样是有点麻烦的,所以在日常的开发
过程中,我们是没必要写额外的代码来实现这种方式,可以用工具(lombok)来实现。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Long id;
private String name;
private int age;
private String address;
}
@Data 这个注解代表实现了所有非final属性的getter/setter方法,以及重写了toString方法和hashCode方法。
@AllArgsConstructor 这个注解代表实现了一个包含全部属性为参数的构造方法(Person(Long id,String name,int age, String address))。
@NoArgsConstructor 这个注解代表实现了一个没有任何参数的构造方法(Person())。
@Builder 这个注解代表实现了上面介绍的那种灵活的创建对象的建造者模式(使用这个注解时需要依赖上面3个注解,原因看这种方式的实现过程就能明白了)。
简单举例
【1】买汽车
【2】造房子
上面的示例是Builder模式的常规用法,导演类Director 在Builder模式中具有很重要的作用,它用于指导具体建造者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合
通过静态内部类方式实现零件无需装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品。
比如:麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户,比第一种方式少了指挥者,主要是因为第二种方式吧指挥者交给用户来操作,产品的创建更加简单灵活。
方法二:
优点:
产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。
将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
具体的建造者类之间是相互独立的,这有利于系统扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则”。
缺点;
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大
应用场景:
建造者和抽象工厂的比较:
单例模式
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
单例模式可以说只要是一个合格的开发者都会写,但是如果要深究,小小的单例模式可以牵扯到很多
东西,比如多线程是否安全,是否懒加载,性能等等。还有开发者知道单例模式的多少种写法。如何
防止反射破坏单例模式
饿汉式
[注]
饿汉式是最简单的单例模式的写法,保证了线程的安全,在很长时间里,我都是饿汉模式来完成单例的,因为够简单,后来才知道饿汉式会有一点问题,如下
public class Hungry {
private byte[] data1 = new byte[1024];
private byte[] data2 = new byte[1024];
private byte[] data3 = new byte[1024];
private byte[] data4 = new byte[1024];
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
[注]
在Hungrey 类中,定义了四个byte数组,当代码一运行,这四个数组就被初始化,并且放入内存了,如果长时间没有用到getInstance方法,不需要Hungrey类的对象,这不是一种浪费吗?开发者希望的是只有用到了getInstance方法,才会去初始化单例类,才会加载单例类中的数据,所以就有了第二种单例模式
优点
由于单例模式只生成一个实例,减少了系统性能开销
单例模式可以在系统设置全局的访问点,优化共享资源访问
单例模式可以说只要是一个合格的开发都会写,但是如果要深究,小小的单例模式可以牵扯很多东西