# 设计模式
# 观察者模式
观察者模式,是面相接口编程的思想,是一种松耦合的体现,首先定义一个接口类,定义出公共行为sayHi
public interface Behavior {
void sayHi(String str);
}
然后定义多个观察者实现该接口,在实际开发中就是实现各自的业务逻辑,这里只展示一个
public class ObserverA implements Behavior{
@Override
public void sayHi(String str) {
System.out.println(str + ",这里是观察者1号");
}
}
然后在定义一个被观察者,保证被观察者中可以持有所有观察者对象的list,在调用时可以依据业务逻辑遍历对象调用方法
public class Subject {
private List<Behavior> list = new ArrayList<>();
public void addObserver(Behavior behavior) {
list.add(behavior);
}
public void sayHi(String str) {
list.forEach(item -> item.sayHi(str));
}
}
测试代码
public static void main(String[] args) {
Subject subject = new Subject();
subject.addObserver(new ObserverA());
subject.addObserver(new ObserverB());
subject.addObserver(new ObserverC());
subject.addObserver(new ObserverD());
// 通知
subject.sayHi("大家好");
}
大家好,这里是观察者1号
大家好,这里是观察者2号
大家好,这里是观察者3号
大家好,这里是观察者4号
当然开发时我们还是更多的借用Spring依赖注入的概念,来实现自动注入,我们可以将每个观察者注册成bean,在类中加上@Component,然后在
被观察者中使用依赖注入,以下两种方式均可
@Autowired
private List<Behavior> list;
@Autowired
public void setList(List<Behavior> list) {
this.list = list;
}
在Spring中的事件发布机制也是基于此,从publishEvent(applicationEvent)中debug源码后发现,Spring中通过层层过滤找到符合的监听器通过for循环调用
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
// 异步调用
executor.execute(() -> invokeListener(listener, event));
}
else {
// 同步调用
invokeListener(listener, event);
}
}
}
在往下debug时发现invokeListener(listener, event)方法下调用了doInvokeListener(listener,event)方法,
而该方法中调用了onApplicationEvent(event),该方法就是必须要实现的监听器接口中的方法
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
# 装饰者模式
先来想一个场景,现在开了一个奶茶店,奶茶可以随意添加配料,配料有椰果,珍珠,布丁,根据不同的组合价格不同,这时候如果我要依据用户选择的组合来制作奶和计算价格 代码应该怎么写?一堆if else?太难看了,写一个基类为奶茶,然后每种配料写一个子类,然后椰果+珍珠再写一个子类?这样子类太多,然而用装饰者模式就可以很好的解决这个问题
装饰者模式就是在不改变原有对象的情况下,将新功能添加在原对象上面(扩展对象),这种模式比类之间的继承更具有弹性,可自由扩展功能,代码实现如下
先抽象一个奶茶的接口,该接口有两个方法,一个是算钱,一个是获取名称
public interface TeaWithMilk {
public String getName();
public BigDecimal getCost();
}
然后写一个牛奶类当做基础类,并实现接口(以下所有类均实现接口)
public class Milk implements TeaWithMilk{
private String name = "牛奶";
private BigDecimal cost = new BigDecimal(5.00);
public String getName(){ return this.name; }
public BigDecimal getCost() { return this.cost; }
}
然后写一个椰果类,并且在该类中持有牛奶类对象(其他配料类就不写了,与椰果类相同)
public class Coco implements TeaWithMilk {
private TeaWithMilk teaWithMilk;
private String name = "椰果";
private BigDecimal cost = new BigDecimal(1.50);
@Override
public String getName() { return teaWithMilk.getName() + " + " + this.name; }
@Override
public BigDecimal getCost() { return teaWithMilk.getCost().add(this.cost); }
public Coco(TeaWithMilk teaWithMilk) { this.teaWithMilk = teaWithMilk; }
}
在使用的时候,可以通过new对象的方式自由组合顺序和配料,测试代码如下
public static void main(String[] args) {
// 制作一杯椰果+布丁+珍珠的奶茶
Coco coco = new Coco(new Milk()); // 加椰果
Pudding pudding = new Pudding(coco); // 加布丁
TeaWithMilk teaWithMilk = new Pearl(pudding); // 加珍珠
System.out.println(teaWithMilk.getName() + "价格为:" + teaWithMilk.getCost().setScale(2,RoundingMode.DOWN));
// 制作一杯双份椰果+单份布丁的奶茶
Coco coco1 = new Coco(new Milk()); // 加椰果
Coco coco2 = new Coco(coco1); // 加椰果
TeaWithMilk teaWithMilk1 = new Pudding(coco2); // 加布丁
System.out.println(teaWithMilk1.getName() + "价格为:" + teaWithMilk1.getCost().setScale(2,RoundingMode.DOWN));
}
通过装饰者模式就可以在不更改牛奶类的情况下,添加很多口味并且可以自由组合
牛奶 + 椰果 + 布丁 + 珍珠价格为:9.50
牛奶 + 椰果 + 椰果 + 布丁价格为:10.00
# 代理模式
指对象A通过持有对象B,可以替代B的功能。通常持有方式为B实现了一个接口,A也会去实现相同的接口。但A不是真正的实现类,只是负责调用B的方法,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是基于动态代理
类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,JDK动态代理和CGLIB动态代理。
# JDK动态代理
jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。使用jdk的动态代理的要求是被代理类必须有对应实现的接口
假设有这样一个场景,一个厨师想开饭店,但他不可能身兼数职,因此他请了两个人,帮他收银和送菜
public interface Chef {
void cook();
}
然后再有一个类实现该接口
public class A implements Chef{
@Override
public void cook() {
System.out.println("正在做饭……");
}
}
顾客进店后需要先点餐,然后付款,做完菜后还需要送菜,但他不是代理类,原因是它没有实现我们的厨师接口,无法对外服务
public class CookingMasterBoyProxy implements InvocationHandler {
// 目标类,也就是被代理对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里可以做增强
System.out.println("点餐……");
Object result = method.invoke(target, args);
System.out.println("送餐……");
return result;
}
// 生成代理类(该方法写在任意处均可,通常放在工厂类中)
public Object CreatProxyedObj() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。
步骤
- new一个目标对象
- new一个InvocationHandler,将目标对象set进去
- 通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。
public static void main(String[] args) {
CookingMasterBoyProxy cookingMasterBoyProxy = new CookingMasterBoyProxy();
CookingMasterBoy cookingMasterBoy = new CookingMasterBoy();
cookingMasterBoyProxy.setTarget(cookingMasterBoy);
Chef chef = (Chef) cookingMasterBoyProxy.CreatProxyedObj();
chef.cook();
}
Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。
生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法。
这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。
所以它调用的是B的包装类,这个包装类需要我们来实现,它必须实现InvocationHandler,这个接口里面有个方法,它是Target的所有方法的调用入口(invoke)
通过反编译生成后的代码查看。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口
public final class $Proxy0 extends Proxy implements Chef
所以可以认为creatProxyedObj返回的对象为代理对象,该对象中持有InvocationHandler对象,调用InvocationHandler对象中的invoke方法,而InvocationHandler对象中又有target对象,通过反编译查看生成的class文件,可看到下面方法
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
而生成类的为Proxy的子类,调用父类的构造函方法,我们点进去看发现父类中的构造方法为
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
因此可以证明自动生成的类的实例对象中持有InvocationHandler
一个方法代码如下
public final void cook() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。留心看到C在invoke时把自己this传递了过去, InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了) C类中有B中所有方法的成员变量
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.wzp.module.user.test.Chef").getMethod("cook");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
# CGLIB动态代理
代理的关键是一个类能替代另一个类提供相同效果的服务,因此怎么获得被代理对象的方法是关键,java中有两种方式可以获得方法,一是通过实现相同接口,二是继承父类,而jdk的代理就是采取第一种方案,而cglib方式代理则采取第二种方案
public class CglibProxy implements MethodInterceptor {
// 该处代码可以将动态代理生成的class文件,存放在你指定的路径下,方便反编译查看内容
static {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");
}
// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
public Object CreatProxyedObj(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始执行cglib代理增强方法");
return methodProxy.invokeSuper(o,objects);
}
}
cglib 通过生成一个继承TestCglib的代理类,这个类中由四部分组成(复写的父类方法,新生成的每个方法的cglib代理方法,统一的增强方法),这个类中有一个MethodInterceptor对象,在上述代码中CreatProxyedObj传入的this,该对象就包含了增强方法 在代理类中构建“CGLIB”+“$父类方法名$”的方法,该方法的实现就是super.方法名,用于调用真正的方法逻辑
这里的intercept一共四个参数,第一个参数就是当前的代理对象,上面代码的this,第二个参数是被拦截的方法,第三个参数是方法的参数,第四个参数是代理方法,也就是“CGLIB”+“$父类方法名$”这个方法 在intercept中需要调用methodProxy.invokeSuper(o,objects)而不是method.invoke,原因显而易见,因为在生成的代理类中全部重写了父类方法,并且将重写的方法作为代理类方法的入口,如果在intercept中调用的话,会死循环
public class TestCglib {
public void sayHello(String s) {
System.out.println("hello world, hello " + s);
}
}
下面是测试代码
CglibProxy cglibProxy = new CglibProxy();
TestCglib testCglib = (TestCglib) cglibProxy.CreatProxyedObj(TestCglib.class);
testCglib.sayHello("cglib代理");
另外我们可以通过一个小技巧查看代理生成的class文件 我们可以看到生成的类是TestCglib的子类
public class TestCglib$$EnhancerByCGLIB$$94104789 extends TestCglib implements Factory
并且复写了全部共有方法(包括equals等方法)
public final void sayHello(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$sayHello$0$Method, new Object[]{var1}, CGLIB$sayHello$0$Proxy);
} else {
super.sayHello(var1);
}
}
其中this.CGLIB$CALLBACK_0这个属性其实就是统一的增强方法的对象,在下面代码可以看到被强转为MethodInterceptor类型
public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
}
# 单例模式
单例模式是设计模式中最为简单的一种,即整个程序仅拥有一个实例,一般有三个
特点
- 类构造器私有
- 持有自己类型的私有属性
- 对外提供获取实例的静态方法
单例模式有几种写法
# 懒汉式(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
# 饿汉式(线程安全)
由于在类初始化时就加载对象了,容易产生垃圾
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
# 双重校验模式(线程安全)
这里如果只校验一次,会造成两个线程都判断为空,线程阻塞后会生成多个对象,并且为了防止jvm进行指令的重排序对多线程造成影响,因此singleton使用关键字
volatile修饰
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
# 静态内部类的方式(线程安全)
在第一次获取对象时,jvm初始化SingletonInner内部类,并且仅初始化一次,推荐使用这种方式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonInner.instance;
}
private static class SingletonInner {
private static final Singleton instance = new Singleton();
}
# 使用枚举类方式(线程安全)
枚举类在任何时候都是单例的,并且创建时是线程安全的
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance() {
return INSTANCE;
}
public void sayHi() {
System.out.println("hello world");
}
}
以上是单例模式常见的几种写法,但还存在一个问题,在反序列化时,怎么保证对象唯一呢,先看下问题
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton a = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(a);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
Singleton readObject = (Singleton) ois.readObject();
System.out.println(a);
System.out.println(readObject);
}
控制台打印的结果
com.wzp.module.user.test.Singleton@6e8cf4c6
com.wzp.module.user.test.Singleton@12edcd21
解决办法为加上readResolve()方法
反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象
private Object readResolve() throws ObjectStreamException {
return SingletonInner.instance;
}
# 工厂模式
工厂模式主要分为,简单工厂模式,工厂模式和抽象工厂模式。
简单工厂用的最少,因为完全不符合开放封闭原则,所以用的最少,几乎不用。 工厂模式和抽象工厂模式,大体上相同,但针对的场景不同,工厂模式是针对单一产品的,比如某个品牌的电脑(整体),而抽象工厂模式针对产品族,比如,各个品牌的电脑工厂 不仅可以生产自己的主机,也可以生产自己的显示器,键盘等,然后组合成电脑
抽象工厂模式就是工厂模式的扩展,本质上没区别,工厂模式相比于抽象工厂模式更加遵循开放封闭原则,但在复杂场景下会多出许多不必要的类
# 简单工厂模式
首先定义一个Computer接口,这就是工厂生产出来的最终产品
public interface Computer {
String getComputerName();
}
定义DellComputer和LenovoComputer实现类,实现上面的接口,这里只放一个
public class DellComputer implements Computer {
@Override
public String getComputerName() {
return "戴尔电脑";
}
}
然后创建一个工厂类ComputerFactory该类中有一个生产产品的静态方法getComputer
public class ComputerFactory {
public static Computer getComputer(String brand) {
switch (brand) {
case "Dell":
return new DellComputer();
case "Lenovo":
return new LenovoComputer();
}
return () -> "华硕电脑";
}
}
使用时,直接调用getComputer方法并指定需要的品牌,即可
public static void main(String[] args) {
Computer computer = ComputerFactory.getComputer("Dell");
System.out.println(computer.getComputerName());
}
如果我们现在再加一个苹果品牌,我们就要就修改静态方法,增加一种品牌的判断,不符合开放封闭原则
# 工厂模式
为了增强简单工厂模式的扩展能力,所以我们抽象出来一个工厂接口,然后各个品牌的工厂去实现这个工厂接口,每个工厂只负责生产自己品牌的电脑
先定义一个电脑工厂接口ComputerFactory其中有一个getComputer方法用来生产电脑
public interface ComputerFactory {
Computer getComputer();
}
分别定义戴尔工厂DellFactory和联想工厂LenovoFactory,每个工厂只负责生产自己品牌的电脑
public class DellFactory implements ComputerFactory {
@Override
public Computer getComputer() {
return new DellComputer();
}
}
调用方法如下,new 一个品牌工厂出来,用这个工厂生产的电脑,就是该品牌的电脑
public static void main(String[] args) {
ComputerFactory computerFactory = new DellFactory();
Computer computer = computerFactory.getComputer();
System.out.println(computer.getComputerName());
}
通过工厂模式,我们去除了简单工厂模式中工厂方法中的品牌参数,不再需要判断参数来确定生产品牌,现在我们new完工厂对象后,所生产的品牌就已经确定了 如果这时候我们需要加一个新品牌,我们只需要新创建一个工厂类实现ComputerFactory接口,使用的时候new这个工厂对象就可以了
# 抽象工厂模式
现在细化工厂产品,每一个工厂不光可以生产电脑,也可以生产鼠标,键盘等产品,现在应该怎么扩展,共两种办法
- 使用工厂模式,创建鼠标产品接口,然后再创建对应鼠标的工厂接口,然后各个品牌的鼠标工厂实现工厂接口,生产键盘则再走一遍该流程
- 使用抽象工厂模式,同样创建鼠标接口,然后在已有的工厂接口中添加在生产鼠标方法,在各个品牌工厂中增加生产鼠标的方法实现
以上两种方法各有优缺点,工厂模式完全遵循了开放封闭原则,但因此多了很多不必要的工厂接口和对应实现类,降低了代码的可读性,而抽象工厂模式则在扩展新产品时, 需要修改很多工厂实现类,违反了开放封闭原则,实际开发中选择哪种模式根据业务场景选择,下面是抽象工厂的实现方法
现在增加键盘产品,首先创建一个键盘产品接口keyboard
public interface Keyboard {
String getKeyboardName();
}
分别创建戴尔DellKeyboard和联想LenovoKeyboard键盘产品实现类
public class DellKeyboard implements Keyboard {
@Override
public String getKeyboardName() {
return "戴尔键盘";
}
}
然后在工厂接口中增加生产键盘方法,这里将电脑工厂概念换成了品牌工厂的概念
public interface BrandFactory {
Computer getComputer();
Keyboard getKeyboard();
}
然后在各个品牌工厂中增加生产键盘的方法实现
@Override
public Keyboard getKeyboard() {
return new DellKeyboard();
}
最后调用方式和工厂模式相同
public static void main(String[] args) {
BrandFactory brandFactory = new DellFactory();
Computer computer = brandFactory.getComputer();
Keyboard keyboard = brandFactory.getKeyboard();
System.out.println(computer.getComputerName());
System.out.println(keyboard.getKeyboardName());
}
# 建造者模式
建造者模式主要用于复杂对象的构建,如果创建一个对象时需要传入很多个参数,并且构建这个对象的过程是规律的,则可以考虑使用建造者模式。
建造者模式和抽象工厂模式的区别
建造者模式和抽象工厂模式同样属于创造类设计模式,同样封装了对象的构建过程,但抽象工厂生产的产品为产品族,属于一系列的产品(例如上面例子中的戴尔品牌下的各种产品)。而建造者模式重点为组装过程(逻辑),可以理解为使用工厂模式生产零件,建造者模式负责组装
# 传统建造者模式
传统建造者模式共分为四个角色
- Product: 最终要创建的产品
- Builder:建造者类的接口,定义了一些制造行为,其中包括
builder()方法,用于获取最终生产的产品 - BuilderImpl:建造者的实现类(可以有多个实现类,例如本例子中
GameComputerBuilder和WorkComputerBuilder) - Director:指挥者,负责具体的构建逻辑,
Builder只负责准备零件,真正的组装逻辑由Director负责,以此来实现构建行为的解耦
下面看下具体实现方式
先定义一个具体产品Computer,这就是本例中最终生产的产品,省略了getting和setting方法
public class Computer {
private String cpu;
private String displayCard;
private String ram;
private String disk;
}
定义生产者接口ComputerBuilder
public interface ComputerBuilder {
// 装CPU
void installCPU();
// 装显卡
void installDisplayCard();
// 装内存
void installRAM();
// 装硬盘
void installDisk();
// 返回最终产品
Computer builder();
}
上述接口的具体实现这里只展示GameComputerBuilder
public class GameComputerBuilder implements ComputerBuilder {
private Computer computer;
public GameComputerBuilder() {
this.computer = new Computer();
}
@Override
public void installCPU() {
computer.setCpu("i7");
}
// 省略其他接口方法
·····
}
定义一个指挥者ComputerDirector,这里可以自定义构建逻辑
public class ComputerDirector {
// 这里代表了建造逻辑
public void construct(ComputerBuilder computerBuilder) {
computerBuilder.installCPU();
computerBuilder.installDisk();
computerBuilder.installRAM();
computerBuilder.installDisplayCard();
}
}
至此所需要的角色全部定义完成,调用方式如下
public static void main(String[] args) {
// 创建一个建造者
ComputerBuilder computerBuilder = new GameComputerBuilder();
// 创建一个领导者
ComputerDirector director = new ComputerDirector();
// 领导者指挥建造者构建产品
director.construct(computerBuilder);
// 获取最终产品
Computer computer = computerBuilder.builder();
}
# 简化版建造者模式(使用调用链方式)
这种方式主要是通过在产品内部定义一个静态类Builder,并使内部类的属性和产品类属性保持一致,调用内部类设置属性方法时,返回该内部类本身,通过调用链设置完参数后,调用build方法,将build属性传递给产品类,并返回最终产品对象, 这里的产品类更像是一个产品模板,通过build自定义调用,来构建同类但有差异的产品
下面是具体实现(省略部分getting和setting方法)。构建时,通过自定义的Computer(Builder builder)构造方法,将builder对象转换为computer对象
public class Computer {
private String cpu;
private String displayCard;
private String ram;
private String disk;
public Computer(Builder builder){
this.cpu = builder.getCpu();
this.displayCard = builder.getDisplayCard();
this.ram = builder.getRam();
this.disk = builder.getDisk();
}
// 内部静态类
public static class Builder {
private String cpu;
private String displayCard;
private String ram;
private String disk;
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
// getting and setting
······
}
}
调用方式如下
public static void main(String[] args) {
Computer computer = new Builder()
.setCpu("I7")
.setDisk("512G-SSD")
.setDisplayCard("RTX2080")
.setRam("32G")
.build();
}
# 适配器模式
适配器模式,是为了将两个不相关的类或接口联系起来,通过调用Adapter类目标方法,间接调用源接口方法,适配器模式有三种实现方式:类的适配器模式、对象的适配器模式、接口的适配器模式。
假设有这样一个场景,现在有一个type-C接口的手机需要充电,但现在只有Micro-usb接口的线,这时候我们就需要一个转换头将Micro-usb转换为type-C接口
定义一个手机类,包含充电方法
public class Phone {
public void charge() {
System.out.println("开始使用type-C接口接收电流...");
}
}
定义一个Micro-usb接口
public interface MicroUsb {
// 充电接口
void charge();
}
# 类的适配器模式
适用场景
如果希望一个类转换为满足另一个接口的类时,使用该方式,本例中就是将Phone的子类adapter转换为满足MicroUsb接口的类
定义一个适配器Adapter,继承Phone类并实现了MicroUsb接口
public class Adapter extends Phone implements MicroUsb {
@Override
public void output() {
System.out.println("使用Micro-usb接口输出电流...");
System.out.println("将Micro-usb转换为Type-C");
// 转换为Type-C后,可以给手机充电了(这里间接的把phone类转换成了MicroUsb接口的实现类)
super.charge();
}
}
最终调用如下
public static void main(String[] args) {
MicroUsb microUsb = new Adapter();
// 开始输出电流
microUsb.output();
}
结果如下
使用Micro-usb接口输出电流...
将Micro-usb转换为Type-C
开始使用type-C接口接收电流...
# 对象的适配器模式
适用场景
当希望将一个对象转换为满足另一个新接口的实例对象时,使用该方式,在新接口的实例对象中,调用源类对象中的方法。本例中MicroUsb的实现类通过持有phone对象,调用charge方法
同样定义一个适配器Adapter,实现MicroUsb接口,并通过构造方法持有phone对象
public class Adapter implements MicroUsb {
private Phone phone;
public Adapter(Phone phone) {
this.phone = phone;
}
@Override
public void output() {
System.out.println("使用Micro-usb接口输出电流...");
System.out.println("将Micro-usb转换为Type-C");
// 转换为Type-C后,可以给手机充电了(这里间接的把phone类转换成了MicroUsb接口的实现类)
phone.charge();
}
}
调用如下
public static void main(String[] args) {
MicroUsb microUsb = new Adapter(new Phone());
// 开始输出电流
microUsb.output();
}
# 接口的适配器模式
适用场景
当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter,用空方法实现所有的接口方法,我们在使用的时候继承这个抽象类,并复写我们需要的方法即可
具体实现很简单,这里就不写了
# 策略模式
策略模式属于行为模式之一,本质上对一系列算法进行封装,根据不同情况进行调用,通常策略模式由三部分构成
- 封装类:通过持有策略接口的实例对象,对策略进行二次封装,防止外部直接对策略进行调用
- 策略接口:定义了策略行为方法
- 策略实现:实现了具体策略方法
先定义一个策略接口EncryptStrategy
public interface EncryptStrategy {
String encrypt(String str);
}
定义一个实现类实现策略接口
public class MD5Strategy implements EncryptStrategy{
@Override
public String encrypt(String str) {
return DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8));
}
}
定义一个封装类,并通过构造方法持有EncryptStrategy对象
public class StrategyContext {
private EncryptStrategy encryptStrategy;
public StrategyContext(EncryptStrategy encryptStrategy){
this.encryptStrategy = encryptStrategy;
}
public String encrypt(String str) {
return encryptStrategy.encrypt(str);
}
}
调用如下
public static void main(String[] args) {
// 传入具体的策略对象
StrategyContext context = new StrategyContext(new MD5Strategy());
String s = context.encrypt("123456");
System.out.println(s);
}
在实际开发中,可以借用Spring容器的概念,将所有算法注册成Bean,在StrategyContext中注入,以此降低对象的数量,同时为了遵循开放封闭原则,可以做如下改动:
在EncryptStrategy接口中加入encryptName方法
String encryptName();
在对应实现类中实现该方法
@Override
public String encryptName() {
return "MD5";
}
在StrategyContext中,通过@Autowired将List<EncryptStrategy>注入进来,遍历对象,将每一个策略对象,以策略名字为key,放入map中。
@Component
public class StrategyContext {
private Map<String,EncryptStrategy> encryptStrategyMap = new HashMap<>();
@Autowired
public void setEncryptStrategyMap(List<EncryptStrategy> encryptStrategyList) {
encryptStrategyList.forEach(encryptStrategy -> this.encryptStrategyMap.put(encryptStrategy.encryptName(),encryptStrategy));
}
// 通过策略名称调用对应对象的行为方法
public String encrypt(String encryptName, String str) {
return this.encryptStrategyMap.get(encryptName).encrypt(str);
}
}
调用如下
String s = strategy.encrypt("MD5","123456");