一. 创建型设计模式
工厂模式
定义
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
何时使用 我们明确地计划不同条件下创建不同实例时。
案例 某公司欲开发一套界面库,可以对软件进行界面美化。用户可以选择不同风格的界面,不同的风格将提供效果不同的按钮、文本框、组合框等界面元素,其结构示意图如下图所示:
为了保证系统的灵活性和可扩展性,请使用工厂方法模式进行系统的设计实现。
类图
代码太多,有需要戳我或留下邮箱
角色 理解模式先得理解这个模式中有哪些角色,这些角色需要承担什么责任,理解清楚了,代码就好实现了。
角色 | 责任 | 对应类图 | 备注 |
---|---|---|---|
抽象工厂 | 定义工厂操作方法 | Factory | |
具体工厂 | 实现抽象工厂,具体工厂细节 | SpringFactory | |
具体产品 | 产品结构 | SpringButton、 | |
产品接口 | I产品都共有的抽取出来 | Spring、ISummer |
深入理解
工厂模式使用起来跟我们面向接口编程习惯非常贴合,这个模式更像我们的一种编程习惯。给产品定义接口,工厂产生接口类型的产品。
建造者模式
定义
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
何时使用 一些基本部件不会变,而其组合经常变化的时候。
案例 现有一家游戏公司要开发一个角色皮肤锻造的新模块,其中不同的锻造方法会生成不同的角色皮肤,这家公司的具体基本需求有以下几个方面:
(1)用于锻造的皮肤碎片是保持不变的。
(2)一个角色皮肤可以由一个体格碎片和一个性格碎片锻造而成,例如苗条碎片和温柔碎片锻造一个新的角色皮肤,比如诸葛亮、孙悟空等。
(3)体格碎片可以是强壮类、苗条类等等,它们需要在游戏中获得;性格碎片可以是温柔型、热情型,它们需要在抽奖箱获得。
某软件公司接受了这个系统模块的开发,使得这个新模块可以达到独立,易扩展,而且便于控制细节风险。
类图
涉及模式部分已经标红,代码太多,有需要戳我或留下邮箱
角色 理解模式先得理解这个模式中有哪些角色,这些角色需要承担什么责任,理解清楚了,代码就好实现了。
角色 | 责任 | 对应类图 | 备注 |
---|---|---|---|
抽象建造者 | 有基本的产品建造过程,返回产品方法 | RoleBuilder | 抽象的类型可以是抽象类或接口 |
具体建造者 | 继承抽象建造者,实现方法 | Starload | 首先要有建造具体产品的能力(拥有建造Role成员变量的方法),还有返回建造对象的方法 |
指挥者 | 描述如何组装产品对象,生产产品 | Director | 怎么组装这些构件 |
具体产品 | 建造的对象 | Role | 应该拥有哪些构件 |
深入理解
- 指挥者Director跟建造者RoleBuilder什么关系?
这个问题不如问为什么存在指挥者?先来说说指挥者大致上是承担什么责任----负责产品的生产过程。怎么理解呢?产品生产我可以缺少零件,我可以选择怎么组装。下面的construct方法就负责生产过程,我可以生产诸葛亮-星航指挥官皮肤,也可以生产黄金分割率皮肤,他们的body都是同一个body。
public class Director {
RoleBuilder builder;
public void construct(Body body,Characte cha,Pathway pathway) {
builder.Buildbody(body);
builder.Buildcharacter(cha);
builder.Buildpathway(pathway);
}
public Director(RoleBuilder builder) {
this.builder=builder;
}
}
原型模式
定义
原型模式(Prototype
Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
何时使用
- 1、当一个系统应该独立于它的产品创建,构成和表示时。
- 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 3、为了避免创建一个与产品类层次平行的工厂类层次时。
- 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
案例 1、设计一个学生类,其中学生类中的成绩存储在另外一个成绩类中,用浅克隆和深克隆分别实现学生类对象的复制。画出类图,并比较浅克隆和深克隆之间的异同。
类图
代码太多,有需要戳我或留下邮箱,这个类图重点讲解深浅克隆
角色 理解模式先得理解这个模式中有哪些角色,这些角色需要承担什么责任,理解清楚了,代码就好实现了。
角色 | 责任 | 对应类图 | 备注 |
---|---|---|---|
客户(Client)角色 | 客户类提出创建对象的请求 | App | |
抽象原型(Prototype)角色 | 这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口 | 这里没有设计 | |
具体原型(Concrete Prototype)角色 | 被复制的对象。此角色需要实现抽象的原型角色所要求的接口 | Student、grade |
深入理解
- 深浅克隆的异同?
两者对于基本数据类型都是值引用(String,int,float等)。
深克隆:对于深克隆,里面所有类类型的都独享一块内存空间,可能外界看来他们的值是相同的,但内存地址不同(hash不同)。
浅克隆:对于浅克隆,里面所有类型都是值引用,类类型是把类对象的地址引用给克隆的对象(同一个对象)。
谨记:调用clone方法并不会通过构造器产生对象,它直接复制内存中的二进制给新的对象。
举例:Student类中有Grade类的对象,浅克隆方式:
@Test
public void testLightClone() throws Exception {
Student xiaoming = new Student("小明", new Grade(60));
Student kangkang = (Student) xiaoming.clone();
System.out.println(xiaoming + "\n" + kangkang);
System.out.println("Student这是同一个对象吗?" + (xiaoming == kangkang));//false
System.out.println("Grade这是同一个对象吗?" + (xiaoming.getGrade() == kangkang.getGrade()));//true
}
/**
* 深克隆,递归复制对象,对象值相同,但是是另一块内存地址
*
* @throws Exception
*/
@Test
public void testDeepClone() throws Exception {
Student xiaoming = new Student("小明", new Grade(60));
Student kangkang = (Student) xiaoming.deepClone();
System.out.println(xiaoming + "\n" + kangkang);
System.out.println("Student这是同一个对象吗?" + (xiaoming == kangkang));//false
System.out.println("Grade这是同一个对象吗?" + (xiaoming.getGrade() == kangkang.getGrade()));//false
}
适配器模式
定义
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。
何时使用
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
- 通过接口转换,将一个类插入另一个类系中。
案例 某系统需要提供一个加密模块,将用户信息(如密码等机密信息)加密之后存在数据库中,系统已经定义好了数据库操作类。为了提高开发效率,现需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码。使用适配器模式使用该加密模块,实现在不修改现有类的基础上重用第三方加密方法 。
类图
代码太多,有需要戳我或留下邮箱
角色 理解模式先得理解这个模式中有哪些角色,这些角色需要承担什么责任,理解清楚了,代码就好实现了。结合定义看应该有哪些角色
角色 | 责任 | 对应类图 | 备注 |
---|---|---|---|
目标接口 | 当前系统希望的操作接口 | DataOperator | 类型可以是抽象类或接口 |
适配者 | 它是被访问和适配的现存组件库中的组件接口 | Base64Entry、MD5Entry | 单一的类或者组件(有它自己单一的功能) |
适配器 | 转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口 | XXAdapter | 适配接口和单一的类,希望通过接口可以使用单一类的功能 |
package com.itxin.adapter;
import java.util.*;
/**
* 适配器
*/
public class Base64Adapter extends DataOperator {
private Base64Entry base64Entry;
public Base64Adapter() {
base64Entry = new Base64Entry();
}
@Override
public String doEncoding(String password) {
return base64Entry.doBase64(password);
}
}
适配原理:在重写的方法里通过对象调用方法。
深入理解
- 为什么我强调说适配者是单一的?有独立的功能?什么是单一的?
这个类没有继承、没有实现某个接口,就可以说他是独立的,单一的类。适配器的目的就是为了适配----原来系统的有个单一的类,它并没有接口调用,现在希望可以通过统一的接口去使用这个类的功能,我就需要做适配,让这原本并没有关系的类产生联系。这也是它的使用场景之一。
二. 结构型模式
装饰器模式
定义:
装饰器模式(Decorator
Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
何时使用:在不想增加很多子类的情况下扩展类。
案例:我们要实现一个登录的通用模块,但是有的登录界面只需输入账号密码,有的需输入账号密码验证码,有的需输入账号密码验手机号,有的需输入账号密码验证码手机号,请用装饰模式实现这个登录模块,可以适应不同客户的需求,要求具体实现所有功能
类图:
涉及模式部分已经标红,代码太多,有需要戳我或留下邮箱
笔者这里为了使构件重用,还使用了工厂模式,所有的构件(登录框啊,标签啊)都从工厂获取。这里主要讨论装饰模式。
角色:理解模式先得理解这个模式中有哪些角色,这些角色需要承担什么责任,理解清楚了,代码就好实现了。
角色 | 责任 | 对应类图 | 备注 |
---|---|---|---|
抽象装饰类 | 持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口 | UserPassDecorator | 抽象的类型可以是抽象类或接口 |
具体装饰 | 父类是抽象装饰(UserPassDecorator),构建对象,追加责任 | UserPassTelDecorator,, | |
抽象构件 | 规范准备接受附加责任的对象 | Ilogin | |
具体构件 | 定义一个将要接受附加责任的类 | UserPass |
深入理解 多多思考
- 为什么抽象装饰类(UserPassDecorator)跟父接口Ilogin是依赖+组合关系?
类图基本能确定代码有哪些属性、方法、构造器是什么样。依赖关系在代码表示为:局部变量、方法的参数和静态方法的调用。组合关系:依赖+构造器里参数,这样
public abstract class UsePassDecorator implements ILogin {
public ILogin iLogin;
public UsePassDecorator(ILogin iLogin) {
this.iLogin = iLogin;
}
- 被装饰类,装饰类,他们都继承自同一个父类,为什么这么设计?上述代码 装饰类里都有这样一个构造方法?这个构造方法的作用是什么?
你叫人给你的手机贴膜,是不是得把你的手机给别人吧。而这个构造方法就相当于把要贴膜的手机给别人,其实就是把被装饰对象给装饰组件,然后装饰组件才能在这个被装饰对象的基础上添加新的行为。那这个和我们都继承同一个父类有什么关系呢?其实,这里的继承,目的不管是对接口的统一,还有一个目的是为了对象的匹配。大家想象一下,被装饰对象可以被装饰,而新得到的这个被装饰过的对象是不是还可以被装饰?那这个反复被装饰的过程,是不是要求我们这些个被装饰对象应该是可以相互匹配的?就好像我的装饰方法,假如是这样的装饰(被装饰对象)那是不是要求你传递给我哪种被装饰对象,都可以和我参数里的被装饰对象相互匹配是不是应该是这样的?不管是传递基础被装饰对象,还是已经被装饰过得对象,都应该匹配。
强调:多态是手段,我们为了达到我们的目的,所以使用了多态来实现
代理模式
定义
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
何时使用
想在访问一个类时做一些控制。方法增强。
案例 某软件公司开发了一套用户系统,用户可以身份验证,修改个人信息和信息查询,原有需求如下:
(1) 用户需要通过身份验证,只有合法用户才能够使用修改个人信息和信息查询;
随着公司用户群的扩大,公司决定对用户进行分类权限管理,将用户群划分为VIP用户和普通用户,具体需求如下:
(1) 用户需要通过身份验证,普通用户只能够使用修改个人信息;
(2) 用户需要通过身份验证,VIP用户可以使用修改个人信息和信息查询;
类图 代码太多,有需要戳我或留下邮箱
角色:理解模式先得理解这个模式中有哪些角色,这些角色需要承担什么责任,理解清楚了,代码就好实现了。
角色 | 责任 | 对应类图 | 备注 |
---|---|---|---|
真实角色RealSubject | 定义Proxy所代表的实体 | EmployeeServiceRealImpl | |
抽象角色Subject | 定义Proxy和RealSubject的共用接口,这样在任何使用RealSubject的地方都可以使用Proxy | EmployeeService | |
代理角色Proxy | 保存一个实体Subject引用,使得代理可以访问实体 | EmployeeServiceProxy | 对真实角色做增强 |
深入理解
上次讨论的是静态代理:下面聊一聊动态代理。
- 什么是静态代理?
在运行之前就存在字节码文件。
- 什么是动态代理?
在运行之前不存在字节码文件,在运行时候动态加载字节码文件。
两种动态代理实现方式:
方式一 JDK动态代理
方式二. CGLIB动态代理
package com.itxin.spring.proxy;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.cglib.proxy.Enhancer;
import lombok.Setter;
/**
* CGLIB动态代理
*
* @author Administrator
*
*/
public class EmployeeAdviceCGLIB implements org.springframework.cglib.proxy.InvocationHandler {
@Setter
private Object obj;//真实类对象
/**
* 具体的增强细节
*/
@Override
public Object invoke(Object arg0, Method method, Object[] arg2) throws Throwable {
//TODO
return null;
}
/**
* 创建代理对象
*
* @param <T>真实对象类型
* @return
*/
public <T> T getProxyObject() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());// 继承要增强的那个类
enhancer.setCallback(this);
return (T) enhancer.create();// 创建代理对象
}
}
两者之间的区别?
JDK动态代理必须提供接口
CGLIB原理:自己的真实对像的代理类实现真实类,把原来需要增强的方法覆盖重写实现增强。所以它不需要接口。