怕什么真理无穷,进一寸有一寸的欢喜

0%

设计模式

设计模式

设计模式是对一些常见问题解决方案的总结与归纳,设计模式需要满足6大原则,分别是:

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒转原则
  • 里氏替换原则
  • 开放封闭原则
  • 迪米特法则
  • 合成复用原则

其中其他原则都是为了开放封闭原则。

创建型模式

单例模式Singleton

为什么要有单例模式,因为某些类只需要一个实例对象,如果创建太多对象,可能造成资源浪费,程序行为异常,增加GC压力等。那么问题来了,静态变量随类加载,也可以保证只有一个实例,为什么不用静态变量呢?涉及lazy load的思想,如果这个静态变量没有被用到就将其加载到了内存中,比较浪费内存空间,如果使用实例对象,可以在使用到这个单例对象时才将其加载到内存。

应用于各种Mgr与Factory

单例模式的需求为一个类只允许产生一个对象,实现的方式有饿汉式,懒汉式与嵌套类式。

饿汉式

1
2
3
4
5
6
7
public class Singleton1{
private static Singleton1 instance = new Singleton1();
private singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}

基本思路为直接创建好一个私有的静态的对象,私有构造函数,创建一个public的静态方法去返回此对象(静态方法只能访问静态成员)。这种方法的优点是线程安全,缺点为如果不用到getInstance()方法,仍然会创建一个对象,增加开销。不过一般推荐使用

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton2{
private static volatile Singleton1 instance = null;
private singleton2(){}
public static Singleton2 getInstance(){
if(instance == null){
synchronized(Singleton2.class){
if(instance == null){
instance = new Singleton2;
}
}
}
return s;
}
}

基本思路也为创建一个私有静态对象,但一开始不初始,私有构造函数,创建public的静态方法返回此对象,需要加上双重验证,加synchronized是为了保证线程安全,外面加上一层判断是为了提高效率,其中因为new这个语句不是原子性的,为了避免语句重排,出现s没有被初始化就返回的情况,需要让instance被volatile修饰,利用内存屏障保证不重排语句。优点是节约了资源,缺点是写法比较复杂,写错了容易线程不安全。

静态内部类

1
2
3
4
5
6
7
8
9
public class Singleton3{
private singleton3(){}
private static class Holder{
private static instance = new Singleton3();
}
public static Singleton3 getInstance(){
return Holder.instance;
}
}

在饿汉式基础上将对象构造放进内部类中,这样不调用内部类的时候不新建对象。

基本思路为私有构造方法,定义一个私有的静态类,避免其他类访问,直接类名调用,此类中持有外部类的私有静态实例,外部类提供方法,返回内部类中的对象。这样静态类只在被调用时加载一次,因此只有一个对象。优点是不使用getInstance方法不实例化,节约资源。缺点是第一次加载比较慢。

枚举类

1
2
3
4
5
public enum Singleton4 {
INSTANCE;
}
//调用时
Singleton4 s = Singleton4.INSTANCE;

写法简单,调用方便。

JDK中用到的单例模式为:Runtime类,使用getRuntime(),使用的饿汉式。

1
2
3
4
5
6
7
public class Runtime {
private static Runtime currentRuntime = new Runtime();
private Runtime() {}
public static Runtime getRuntime() {
return currentRuntime;
}
}

策略模式

对于普通的排序,只能排数字,如果想要对多种类型进行排序,方法传入的参数需要为Compareble[]类型,然后比较的时候,调用数组的compareTo[]方法即可。但是这样依旧不够灵活,因为传入的类必须覆写compareTo()方法,更灵活的是让排序的策略可以灵活指定。定义比较器,实现Comparotor接口,实现compare()方法。

一般策略模式,需要有一个策略接口,如Comparator接口,策略接口可以有多个实现,策略模式封装的是做一件事不同的执行方式

工厂模式Factory

任何可以产生对象的方法或类,都可以称之为工厂,单例也是一种工厂。

应用:Spring IOC

简单工厂

需求:需要使用不同的交通工具,并且调用他们的go方法。

定义一个交通工具的接口Moveable,定义go方法,让所有交通工具都要实现此接口。为了统一生产交通工具,定义一个简单工厂,里面写上不同的get方法来获取不同的交通工具对象,在Main方法中,通过定义接口类型的Module类,指向工厂方法获取的不同对象,以多态来获取不同对象的方法。

Main中

1
2
3
4
5
public static void main(String[] args) {
//获取生产工具
Moveable m = new SimpleVehicleFactory().getCar();
m.go();
}

简单工厂的方法

1
2
3
4
5
6
7
8
public class SimpleVehicleFactory {
public Car getCar(){
return new Car();
}
public Plane getPlane(){
return new Plane();
}
}

抽象工厂

需求:需要生产一系列产品,如需要生产食品,武器,交通工具。先定义生产的产品的抽象类,然后抽象工厂新建方法返回对应的抽象类。抽象类中只有一个方法,只有一个方法但是用抽象类,是因为食品这些本身是一个具体的概念,是名词。

1
2
3
4
5
6
public abstract class AbstractFactory {
//产生三个不同的产品
abstract Food createFood();
abstract Vehicle createVehicle();
abstract Weapon createWeapon();
}

然后如果是工厂1生产自己系列的产品,如面包,手枪,汽车,让产品继承自对应的抽象类,然后工厂1继承抽象工厂,返回自己生产的类即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ModernFactory extends AbstractFactory {
@Override
Food createFood(){
return new Bread();
}
@Override
Vehicle createVehicle() {
return new Car();
}
@Override
Weapon createWeapon() {
return new AK47();
}
}

在Main方法中,持有抽象工厂的引用,具体的对象实例可以是继承了抽象工厂的那些工厂,然后调用工厂的对应方法即可以获取到对应的内容。

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
//获取生产工具
AbstractFactory f = new ModernFactory();
Vehicle c = f.createVehicle();
c.go();
Food food = f.createFood();
food.printName();
Weapon weapon = f.createWeapon();
weapon.shoot();
}
}

抽象工厂方便于产品族的设计。

建造模式Builder

构建复杂对象

  • 分离复杂对象的构建和表示
  • 同样的创建过程可以创建不同的表示
  • 不同记忆,自然使用

如果地形类中有墙,暗堡,地雷,如下

1
2
3
4
5
public class Terrain {
Wall w;
Fort f;
Mine m;
}

如果想构建不同的地形,可以使用不同的构建器,有一个构建地形的接口,其中有构建墙、暗堡、地雷的方法,当构建完成后,返回一个地形对象。

1
2
3
4
5
6
public interface TerrainBuilder {
TerrainBuilder buildWall();
TerrainBuilder buildFort();
TerrainBuilder buildMine();
Terrain build();
}

对于一个具体的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ComplexTerrainBuilder implements TerrainBuilder {
Terrain terrain = new Terrain();
public TerrainBuilder buildWall() {
terrain.w = new Wall(10, 10, 50, 50);
return this;
}
public TerrainBuilder buildFort() {
terrain.f = new Fort(10, 10, 50, 50);
return this;
}
public TerrainBuilder buildMine() {
terrain.m = new Mine(10, 10, 50, 50);
return this;
}
public Terrain build() {
return terrain;
}
}

聚合一个地形对象,当调用不同的构建方法时对地形对象分批进行构建,返回this即当前构建器对象,最后返回聚合的地形对象。

其中返回一个构建器的接口是因为方便链式编程。在main中调用时,使用链式编程调用非常方便

1
2
TerrainBuilder builder = new ComplexTerrainBuilder();
Terrain t = builder.buildFort().buildMine().buildWall().build();

在Java中应用于属性非常多的对象的构建,如果一个对象有些属性是必须的,但一些属性可有可无,如果每次构建对象 都要传入许多参数,则非常麻烦,可以私有类的构造器,在其内部有一个静态内部类,由静态内部类来创建对象,并对外提供对外部类对象构造的方法。

可以看到,下面的类私有了Person对象的构造方法,这样想要新建一个类,只能使用其静态内部类Builder,有基础信息的构建,也有可选信息的构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Person {
int id;
String name;
int age;
double weight;
int score;
private Person() {}

public static class PersonBuilder {
Person p = new Person();
public PersonBuilder basicInfo(int id, String name, int age) {
p.id = id;
p.name = name;
p.age = age;
return this;
}

public PersonBuilder weight(double weight) {
p.weight = weight;
return this;
}

public PersonBuilder score(int score) {
p.score = score;
return this;
}

public Person build() {
return p;
}
}
}

在main中调用时,如果不想构建哪个属性,直接将其注释掉即可,这样构建非常方便。

1
2
3
4
5
Person p = new Person.PersonBuilder()
.basicInfo(1, "zhangsan", 18)
//.score(20)
.weight(200)
.build();

总结:用于被构建类非常复杂的情况,构建工具类聚合一个被构建类对象,有返回构建工具类的不同构建方法,便于链式编程,最后有一个返回返回被构建类对象的方法。

原型模式Prototype

又叫克隆模式,Object.clone()

一个类想要被克隆,需要

  1. 实现Cloneable标记型接口(其中没有方法)

    若不实现接口,编译不出错,调用报异常

  2. 被克隆的类需要重写clone方法

    因为Object类中的clone方法为native

一般用于一个对象的属性已经确定,需要产生很多相同对象的时候

克隆的只是基本属性,如果被克隆的类中有其他对象的引用,则拷贝的只是此对象的引用。因此是浅克隆。

在main中输出,p与p2的Location对象地址相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person implements Cloneable{
public String name = "zhangsan";
public int age = 10;
public Location location = new Location("nj","gulou");
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Person p = new Person();
Person p2 = (Person) p.clone();
System.out.println(p2.location.city+" "+ p2.location.street);
System.out.println(p2.location == p.location);
}
}
class Location{
String city;
String street;
public Location(String city, String street){
this.city = city;
this.street = street;
}
}

为了实现深克隆,需要让Location也实现Cloneable接口。

1
2
3
4
5
6
7
8
9
10
11
12
class Location implements Cloneable{
String city;
String street;
public Location(String city, String street){
this.city = city;
this.street = street;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

而在Person类中,将location单独clone

1
2
3
4
5
6
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();//用this.clone循环调用,栈溢出
p.location = (Location) this.location.clone();
return p;
}

Location中的String类型的属性不用进行深克隆,因为字符串在常量池中,String类引用不可变。若使用的是StringBuilder,则Location中新老对象使用的同一个StringBuilder引用。那么深克隆时候也需要把StringBuilder进行深克隆。

结构型模式

装饰模式Decorator

IO流中的Writer,有人说是装饰。如果要对一个类的功能进行装饰,即增加一些功能,要是使用继承,会产生类爆炸的现象,那么使用一个抽象类继承装饰抽象类,需要持有一个装饰类对象,在装饰类的实现类中,调用被装饰类的自己的方法,然后加入一些字自己的方法。

门面模式Facade

许多类之间有复杂的联系,如果Main类中去跟每个类交互,逻辑非常复杂。而使用一个类,封装所有的类之间的逻辑,对外只暴露一些接口,相当于一个门面。

享元模式Flyweight

共享元数据,将小对象放入池子中,需要用到的时候,从池子中取即可。

组合模式Composite

树状结构专用模式。

将Node组合到Branches类中。

如有节点的抽象类,其中有方法p(),用来打印当前的节点值

1
2
3
4
abstract class Node{
String name;
public abstract void p();
}

有叶子节点,直接打印值

1
2
3
4
5
6
7
8
class LeafNode extends Node{
public LeafNode(String name){
this.name = name;
}
public void p() {
System.out.println(this.name);
}
}

如果是枝干节点,则下面有枝干或者叶子,因此持有一个Node的集合,对外提供将Node加入集合的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Branches extends Node{
//持有一系列的Node
public List nodes = new LinkedList();
public Branches(String name){
this.name = name;
}
public void add(Node n){
nodes.add(n);
}
public void p() {
System.out.println(name);
}
}

在遍历树的时候,传入一个节点Node,传入一个当前的高度(便于去打印树的深度)

使用递归的写法,base case为节点为空,直接返回;打印当前高度,打印当前节点。如果当前节点是枝干节点,获取当前枝干节点的节点集合,然后对集合中的所有节点递归调用遍历函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//遍历树
public static void tree(Node n,int height){
if (n == null)
return;
for (int i = 0; i < height; i++) {
System.out.print("--");
}
n.p();
//如果是枝干
if (n instanceof Branches){
for(Node node : ((Branches)n).nodes){
tree(node,height+1);
}
}
}

代理模式Proxy

代理的含义是比如去买酒,不需要自己去找酒的生产商,而是找到其代理人即可。

静态代理。

有一个可移动的接口,其中有move方法,坦克类实现此接口并覆写方法,为了记录坦克类的运行时间,可以使用继承,但是这样非常臃肿而且不利于功能的复用,比如要记录时间,日志,时间与日志,就需要3个继承类。这时候使用静态的代理,一个时间代理类继承Moveable接口,然后聚合一个Moveable,在其move方法中,增加自己功能,再调用持有的Moveable的move方法。因为此代理也是一个Moveable,因此是可以被其他的代理类所代理的,这样增加功能非常方便,静态代理有点类似于装饰器模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
interface Moveable{
public void move();
}
class Tank implements Moveable{
public void move() {
System.out.println("Tank wuwuwuu");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MoveableTimeProxy implements Moveable{
//聚合一个引用
Moveable m;
public MoveableTimeProxy(Moveable m){
this.m = m;
}
public void move() {
Long start = System.currentTimeMillis();
m.move();
Long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
class MoveableLogProxy implements Moveable{
//聚合一个引用
Moveable m;
public MoveableLogProxy(Moveable m){
this.m = m;
}
public void move() {
System.out.println("开始移动");
m.move();
System.out.println("结束移动");
}
}
public class Main1 {
//静态代理
public static void main(String[] args) {
Moveable tank = new Tank();
//代理嵌套代理
new MoveableTimeProxy(new MoveableLogProxy(tank)).move();

}
}

动态代理

想要日志代理不仅可以代理可以移动的,还可以代理任意类型的。本质是分离代理行为与被代理对象。因为不知道被代理的类是什么类型,其中有什么方法,需要使用jdk的动态代理。

动态代理不是改变原来类的代码,而是生成一个新的代理类。

仍然定义一个Moveable接口,坦克类实现此接口。为了获得代理类,使用Proxy的newProxyInstance方法获得,传入3个参数,类加载器接口的Class数组被调用时的处理器,即调用被代理对象的方法时该如何做。类加载器写被被代理类的加载器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface Moveable{
public void move();
}
public class Tank implements Moveable{
public void move() {
System.out.println("tank wuwuuw");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Tank tank = new Tank();
//生成一个动态代理类
Moveable m = (Moveable) Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Moveable.class}, new LogProxy(tank));
m.move();
}
}
class LogProxy implements InvocationHandler{
Tank tank;
public LogProxy(Tank tank){
this.tank = tank;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//写代理部分的逻辑
System.out.println("method "+method.getName()+" start");
Object o = method.invoke(tank,args);
System.out.println("method "+method.getName()+" end");
return o;
}
}

可以看到生成代理对象后,调用其move方法,可以获取如下输出

1
2
3
method move start
tank wuwuuw
method move end

但是开始与结束的逻辑写在了InvocationHandler里面的invoke方法,调用move方法时却调用到了invoke方法。原理是生成的动态代理类中,因为其要实现Moveable接口,因此实现了move方法,在此方法中,调用了传入的InvocationHandler的invoke方法。

其中对于invoke方法,通过method可以拿到其方法名,对应不同的方法可以有不同的方法对应。args为往方法里传递的参数,proxy为生成的代理对象m,被代理对象被调用事件是method.invoke,传哪个引用相当于调用哪个引用的方法,method.invoke(tank,args)相当于对坦克对象调用move方法,返回类型与传入引用被调用方法的返回类型相同,此处move返回的是空值,那么这里返回的object也为空。

总的来说,调用的逻辑是,当调用生成的m对象的move方法,其实调用的是传入的InvocationHandler的invoke方法,在其中有自己写的代理的方法,当要使用被代理类的方法时,使用method.invoke,传入被代理对象的引用与参数,返回其原方法的返回值,其实调用的是Tank类的move方法。

而生成动态代理类的过程,底层是使用的asm来实现的,其为二进制字节码操作类库。因此不管用什么语言写,只要能生成二进制字节码文件,就可以在JVM运行,如scala,kotlin等。

JDK反射的动态代理必须面对接口。通过接口来指定代理类生成的接口,否则不知道需要生成哪些方法。

而如果使用CGLIB方式(Code Generation Library)生成,不要求被代理类实现接口,生成的动态代理类为被代理类的子类,因此如果被代理的类被final修饰,是无法使用这种方式来动态代理的。cglib底层也用的asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Tank.class);//设置父类
enhancer.setCallback(new TimeMethodInterceptor());
Tank tank = (Tank)enhancer.create();
tank.move();
}
}
//相当于InvocationHandler
class TimeMethodInterceptor implements MethodInterceptor{
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//o为生成的动态代理类对象,其为被代理类的子类
System.out.println(o.getClass().getSuperclass().getName());
System.out.println("before");
Object result = null;
result = methodProxy.invokeSuper(o,objects);//调用原来的
System.out.println("after");
return result;
}
}

动态代理可以对所有类型的对象执行一定的功能。可以在原来的功能上切入自己定义的功能,不用更改原来类的代码,叫做AOP,Aspect Oriented Programming,面向切面编程,使用的CGLIB的方式。

切面是往哪里加代码的集合。

Spring的AOP配置,需要maven导包org.aspectj

方式一:配置文件配置

1
2
3
4
5
6
7
8
<bean id="timeProxy" class="com.zc.dp.spring.v1.TimeProxy">bean>
<aop:config>
<aop:aspect id="time" ref="timeProxy">
<aop:pointcut id="onmove" expression="execution(void com.zc.dp.spring.v1.Tank.move())"/>
<aop:before method="before" pointcut-ref="onmove"/>
<aop:after method="after" pointcut-ref="onmove"/>
aop:aspect>
aop:config>

pointcut代表在哪个点且,此处为执行Tank的move()方法时进行切面,然后指定在此切面前面执行before方法,后面执行after方法,去ref指定的id为timeProxy的类中去寻找这两个方法,如果想执行且多个方法或者多个类,可以使用.*的方式。这样如果想添加新的功能,非常方便。

方式二:注解配置

在要切的类上加入@Aspect注解,在要之前与之后执行的方法前加入@Before@After注解。比起配置文件实现更方便。

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
//表示是一个要往里面切的类
public class TimeProxy {
@Before("execution(void com.zc.dp.spring.v2.Tank.move())")
public void before(){
System.out.println("before "+System.currentTimeMillis());
}
@After("execution(void com.zc.dp.spring.v2.Tank.move())")
public void after(){
System.out.println("after "+System.currentTimeMillis());
}
}

当Tank被final修饰时,报错

1
Could not generate CGLIB subclass of class com.zc.dp.spring.v2.Tank: Common causes of this problem include using a final class or a non-visible class;

适配器模式Adapter(Wrapper)

接口转换器,一个类不能直接访问另外一个类,中间加一个转换。

  • 电压转接头

  • java.io

    BufferedReader,将字节流转为字符流

  • JDBC转为ODBC,再访问SQL Server,即JDBC与ODBC的Bridge

  • ASM Transformer

    Reader直接传给Writer,相当于复制,这时候如果多加一层Adapter,可以自定义一些功能,也可以叫做适配器。

误区:常见的Adapter类反而不是Adapter,如WindowAdapter,KeyAdapter

在awt中,如果想要监听窗口,直接new WindowListner接口,需要重写6个方法,但很多时候只关心其中几个方法,这时候,WindowAdapter是一个抽象类,实现WindowListner,将全部方法实现为空,这时候继承WindowAdapter只重写关心的方法即可。

常见的带Adapter的类只是为了方便编程而已。

桥接模式Bridge

双维度扩展

  • 分离抽象与具体
  • 用聚合方式(桥)连接抽象与具体

抽象类的树与具体类的树分别发展,但是在抽象类中聚合一个实现类。

需求:礼物Gift有礼物的实现类GiftImlpl,而礼物下有分支温暖的,冷酷的礼物等。而礼物的实现类GiftImlpl下有花,书等。如果子类使用继承,比如一个温暖的花,冷酷的花,可以有多种继承方式,这时候会产生类爆炸。

如Gift抽象类在自己发展,其实现类GiftImlpl也在发展,这时候在Gift类中聚合一个GiftImlpl。就是用聚合代替继承

对于Gift抽象类

1
2
3
public abstract class Gift {
GiftImpl impl;
}

对于其具体类GiftImlpl

1
2
public class GiftImpl {
}

比如Gift下有WarmGift,需要传入一个礼物的实现类

1
2
3
4
5
public class WarmGift extends Gift {
public WarmGift(GiftImpl impl) {
this.impl = impl;
}
}

在具体应用时,将具体的实现类传入抽象类即可。

1
2
3
4
public void chase(MM mm) {
Gift g = new WarmGift(new Flower());
give(mm, g);
}

行为型模式

策略模式Strategy

对做同一件事情,有不同的策略。如坦克的开火,可以有普通的开火,也可以四个方向开火。

1
2
3
interface Fire{
void fire();
}

在main中持有Fire,传入不同的Fire实现类,有不同的策略实现。

责任链模式COR

场景:对字符串进行敏感词过滤操作。如有消息类Msg,其字符串中包含敏感词

其中尖括号中的内容可能会被直接访问,而996算敏感词,网站需要对这两种情况进行过滤。

定义一个Filter接口,定义抽象方法doFilter,需要传入消息类对象。

1
2
3
interface Filer{
void doFilter(Msg msg);
}

因为可能有多种敏感词过滤的情况,因此定义多个类,分别实现Filter接口,来覆写自己的doFilter方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class HTMLFilter implements Filer{
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace('<','[');
r = r.replace('>',']');
msg.setMsg(r);
}
}
class SensitiveFilter implements Filer{
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace("996","955");
msg.setMsg(r);
}
}

为了更好封装多个Filter,定义一个FilterChain,可以添加Filter与获取所有的Filter。其中add方法返回的是当前的FilterChain对象,便于链式编程

1
2
3
4
5
6
7
8
9
10
11
12
class FilterChain{
List filers = new ArrayList();
public FilterChain add(Filer f){
filers.add(f);
return this;
}
public void doFilter(Msg msg){
for (Filer filer : filers) {
filer.doFilter(msg);
}
}
}

然后在主方法中,新建FilterChain对象,添加对应的Filter,调用fc的doFilter方法即可。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("大家好:),《script》,欢迎访问 zc.com,大家都是996");
//处理msg
FilterChain fc = new FilterChain();
fc.add(new HTMLFilter())
.add(new SensitiveFilter());
fc.doFilter(msg);
System.out.println(msg.getMsg());
}

整个过滤逻辑如下图所示,每个Filter有自己的责任,串成了链条的样子。

但是当需要有另外的一个责任链fc2,也需要进行处理,为了让fc1与fc2串在一起,可以让FilterChain本身也实现Filter接口,这样可以让fc1直接add责任链fc2,基本逻辑是因为fc2本身也是一个Filter,这样将不同的责任链都串在了一起。

1
2
3
4
5
6
7
8
9
10
11
12
class FilterChain implements Filer{
List filers = new ArrayList();
public FilterChain add(Filer f){
filers.add(f);
return this;
}
public void doFilter(Msg msg){
for (Filer filer : filers) {
filer.doFilter(msg);
}
}
}

在main方法中

1
2
3
4
5
6
7
8
9
FilterChain fc = new FilterChain();
fc.add(new HTMLFilter())
.add(new SensitiveFilter());
FilterChain fc2 = new FilterChain();
fc2.add(new FaceFilter())
.add(new URLFilter());
//精髓!!!
fc.add(fc2);
fc.doFilter(msg);

某个Filter决定是否向下继续走。

做法是让Filter的doFilter方法返回一个布尔类型,返回真表示继续进行,返回假表示终止进行。

在FilterChain中,循环的逻辑中,当一个Filter返回的结果为假,表示不用再继续了,直接返回假;不然等全部结束了返回真。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Filer{
boolean doFilter(Msg msg);
}
class FilterChain implements Filer{
List filers = new ArrayList();
public FilterChain add(Filer f){
filers.add(f);
return this;
}
//关键
public boolean doFilter(Msg msg){
for (Filer filer : filers) {
if(!filer.doFilter(msg))
return false;
}
return true;
}
}

在Servlet中,有Filter,其功能为可以同时处理request与response,同时处理的顺序为request1,request2,…,requestn,responsen,…,response2,response1。是一个递归的过程,那么需要在执行当前resquest的时候,知道下一个需要执行哪个Filter,那么需要拿到当前的FilterChain,在Filter接口的doFilter方法中,除了需要传入Response与Request,还需要传入FilterChain,以便去调用下一个的Filter。而在FilterChain中,维护有当前执行到的index,当index超出容器范围,直接返回。执行当前index对应的Filter,让index自增,返回当前filter执行执行的结果。在每个Filter中,先执行对Request的操作,然后可以选择是否执行下一个Filter,如果要就调用持有的FilterChain对象的doFilter方法,然后递归执行完毕后,再执行当前的Response。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Request{
String str;
}
class Response{
String str;
}
interface Filer{
boolean doFilter(Request request,Response response,FilterChain fc);
}
class HTMLFilter implements Filer {
public void doFilter(Request request,Response response,FilterChain chain) {
String r = request.str;
r = r.replace('<','[');
request.str = r.replace('>',']');
//执行下一个
chain.doFilter(request,response);
response.str += "--HTMLFilter";
}
}
class SensitiveFilter implements Filer {
public boolean doFilter(Request request,Response response,FilterChain chain) {
String r = request.str;
r = r.replace(":)","^v^");
chain.doFilter(request,response);
response.str += "--SensitiveFilter";
return true;
}
}
class FilterChain {
List filers = new ArrayList();
//持有个位置变量,指向当前执行的Filter位置
int index = 0;
public FilterChain add(Filer f){
filers.add(f);
return this;
}
//关键
public boolean doFilter(Request request,Response response){
//base case
if (index == filers.size())
return false;
//处理当前的
Filer f = filers.get(index);
index++;
//传给当前Filter的是当前的chain对象
return f.doFilter(request,response,this);
}
}

可以看到,如果在实现的Filter中调用了FilterChain,责任链才会继续往下走,否则会处理当前的response并返回,在main方法中

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Request request = new Request();
request.str = "\"大家好:),《script》,欢迎访问 zc.com,大家都是996\"";
Response response = new Response();
response.str = "response";
FilterChain chain = new FilterChain();
chain.add(new HTMLFilter()).add(new SensitiveFilter());
chain.doFilter(request,response);
System.out.println(request.str);
System.out.println(response.str);
}

这样就可以达到处理的要求了,总结就是思路为递归,需要拿到当前的chain,是否继续往下走的权利在每一个Filter中。

总结:控制责任链是否往下走有两种方式

  1. Filter接口的doFilter方法返回布尔类型的值,在FilterChain中对每个Filter的方法返回值判断,有false就停止
  2. Filter接口的doFilter方法传入FilterChain,FilterChain中有指针,指向当前下一个要执行的Filter,当指向末尾返回,不然执行Filter,指针后移,对执行的Filter传入当前FilterChain对象。控制的权利交给Filter,如果想要继续执行,则调用FilterChain的doFilter方法,否则不调用就不往下走了

观察者模式Observer

如果有一个小孩,哭了之后需要观察者做出不同的反应。做法是小孩哭了后,产生一个事件,将事件传给所有的观察者,依据不同的事件来做出不同的反应。观察者模式需要具有的3个基本类是Source(被观察类),Observer(观察类),Event(事件类),被观察者生产一个事件,传递给观察类接口,接口类方法目的是回调具体实现子类中的方法,当Observer观察到Event的时候,产生具体的反应。

对于事件类,需要知道是哪个被观察者,因为可能监听着多个被观察者。Event类中一般需要有getSource的方法。观察者拿到Event对象后,如果需要知道Source,使用getSource方法就可以,这样可以根据Source来处理相应的处理。这样观察者与被观察者解耦合。

应用:UI界面,很多都是观察者模式。如点击一个Button,产生一个事件Event,调用Observer的方法来处理事件。

很多系统中,Observer与责任链共同负责对事件的处理。其中的一个observer负责是否将事件进一步处理。

调停者模式Mediator

许多类之间有许多联系,为了简化类之间的关联,抽出一个类专门与其他类来通信,这个类称为调停者。

门面模式对外,调停者模式对内,可以是同一个。

实际应用:消息中间件MQ(Message Queue)。将消息统一放在MQ中,谁需要消息谁去拿,进行解耦。

迭代器模式Iterator

用于容器与容器遍历。

集合物理实现只有数组跟链表,为了方便操作不同的集合,让其均继承自一个接口,而为了遍历不同的容器,如何遍历只有容器自己清楚,因此实现的过程封装在不同的容器内部,只对外暴露接口。迭代器接口有hasnext与next方法。在集合接口中,提供获取迭代器的方法。

1
2
3
4
5
public interface Collection_ {
public void add(Object o);
public int getSize();
public Iterator_ iterator();
}

对于具体的实现类,比如LinkedList,提供具体的实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class LinkedList_ implements Collection_{
class Node{
public Object value;
public Node next;
public Node(Object value){
this.value = value;
}
}
private Node head = null;
private Node tail = null;
private int size = 0;
public void add(Object o){
Node cur = new Node(o);
cur.next = null;
if (head == null){
head = cur;
tail = cur;
}else {
tail.next = cur;
tail = cur;
}
size++;
}
public int getSize(){
return size;
}
public Iterator_ iterator() {
return new LinkedListIterator();
}
private class LinkedListIterator implements Iterator_{
private Node curIte = head;
public boolean hasNext() {
if (curIte == null)
return false;
return true;
}
public Object next() {
Object o = curIte.value;
curIte = curIte.next;
return o;
}
}
}

其中LinkedList中的迭代器类为私有的内部类,因为此类只会在当前啊类中使用,而且为了方便访问外部类中的属性与方法,将其进行内部类的封装。而在main方法中,调用的流程如下

1
2
3
4
5
6
7
8
9
Collection_ c = new LinkedList_();
for (int i = 0; i < 15; i++) {
c.add(i);
}
System.out.println(c.getSize());
Iterator_ it = c.iterator();
while (it.hasNext()){
System.out.print(it.next()+" ");
}

如果需要进一步扩展,不指定数据的类型为Object,可以使用泛型。

访问者模式Vistor

结构不变的情况下动态改变对于内部元素的动作。

具体应用:编译器。对抽象语法树的每个节点,传入Vistor,由Vistor来做具体的检查。单一职责原则。

需求:电脑的内部部件是固定的,比如有CPU,内存,主板,如果有不同的人需要去电脑店,电脑店给不同的人有不同组件的优惠,如果是写在每个电脑组件类的内部来判断,则需要有非常多的if else,而且当多一个人以后,就要增加代码,这样将判断的代码封装到一个Vistor中,在每个电脑组件中,需要有接收一个Vistor的方法,还要有调用vistor中优惠价格的方法。

电脑组件定义如下

1
2
3
4
5
abstract class ComputerPart {
abstract void accept(Visitor v);
//可以有其他的方法
abstract double getPrice();
}

对于一个电脑,以其只有3个组件为例,对所有的组件都传入同一个vistor

1
2
3
4
5
6
7
8
9
10
public class Computer {
ComputerPart cpu = new CPU();
ComputerPart memory = new Memory();
ComputerPart board = new Board();
public void acccept(Visitor v) {
this.cpu.accept(v);
this.memory.accept(v);
this.board.accept(v);
}
}

对于电脑组件具体的实现类,是什么部件就调用Vistor的看什么部件的方法。一定会有accpet方法,传入一个Vistor。

1
2
3
4
5
6
7
8
class CPU extends ComputerPart {
void accept(Visitor v) {
v.visitCpu(this);
}
double getPrice() {
return 500;
}
}

对于Vistor接口

1
2
3
4
5
interface Visitor {
void visitCpu(CPU cpu);
void visitMemory(Memory memory);
void visitBoard(Board board);
}

其具体的实现类,拿到对应组件的价格后,进行优惠即可。

1
2
3
4
5
6
7
8
9
10
11
12
class PersonelVisitor implements Visitor {
double totalPrice = 0.0;
public void visitCpu(CPU cpu) {
totalPrice += cpu.getPrice()*0.9;
}
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice()*0.85;
}
public void visitBoard(Board board) {
totalPrice += board.getPrice()*0.95;
}
}

在main方法中

1
2
3
4
5
public static void main(String[] args) {
PersonelVisitor p = new PersonelVisitor();
new Computer().acccept(p);
System.out.println(p.totalPrice);
}

可以看到,其本质是对多个固定组件下,不同组件的if else的封装,适用于组件固定,但每个组件有不同情况的场景下,如果组件频繁增加,则Vistor接口改动也非常频繁,不适用于组件变化多的情况。

ASM框架,相当于使用Java语言来操作字节码文件,可以自己定义Vistor来读字节码文件,也可以来动态写字节码文件。

对于ASM,用Reader去读字节码文件,然后用Writer(一个Visitor)来写字节码文件,如果想要自己定制写的过程,将Reader传给自己定义的Adapter,将Adapter传给Writer,责任链模式。ASM就是用的Visitor,多个Visitor之间形成了链条。利用自己定义的Adapter,可以自己在原来的类字节码文件中的指定方法前后增加内容,这样生成的新的类变为原来类的动态代理类。

命令模式Command

封装命令,结合责任链实现undo(回滚)

别名:Action / Transaction

Command中封装了多个命令,常见的有执行与回滚,对于每个Command,需要能够将命令回退,在undo命令中记录了与execute中相反的操作。

常见的实现:文本编辑器。这时候相当于实现了一次execute,一次undo,但是为了实现一连串的undo,使用责任链。将所有的Commond放入一个集合,当实现一次回退,执行一个Commond中的undo,再实现一次则继续执行一个。

  • 多次undo

    Command与责任链

    将command加入集合,然后逐个取出执行。如果是多次undo,需要倒着过来做

  • 事务回滚

    command与记忆

  • 宏命令(多个命令组成)

    command与组合Composite,因为宏命令是树状结构

备忘录模式Memento

记录状态,便于回滚

有一个原对象,使用Memento记录其状态。

  • 记录快照(瞬时状态)
  • 存盘

模板方法Template Method

模板方法,钩子函数

相当于定好了模板,具体的实现自己来写。

优点是子类不用改变父类中的逻辑框架,但是通过重写部分方法可以实现自己的功能。父类重框架,子类重功能

重写一个方法,系统自动调用,如awt中的paint。在父类中有个方法,调用了method1与method2,子类去继承父类的时候,只需要重写method1与2,在父类中被自动调用。

如下,父类中m方法调用了op1与op2,但是是抽象的,子类中继承父类重写此方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class F{
public void m(){
op1();
op2();
}
abstract void op1();
abstract void op2();
}
class C extends F{

void op1() {
System.out.println("op1");
}

void op2() {
System.out.println("op2");
}
}

在主函数中,多态,调用父类的m方法,执行的是子类中重写的内容。

1
2
F f = new C();
f.m();

状态模式State

根据状态决定行为

如果一个类中很多行为都是要根据其状态决定,那么就将state抽象出来,在类中调用state的相应方法即可。类聚合的是state的实现类。

如果在一个类中,固定的几个方法都要根据不同的状态来做出不同的反映,那就把具体的状态给抽象出来,避免自己类的方法过于臃肿。如果自己类的方法会一直扩展,则不适合用State模式。

在设计模式书中的例子是,TCP连接中有open,listen,close等固定几个方法,根据TCP连接状态有不同的反应。

state类如下

1
2
3
4
abstract class State{
abstract void smile();
abstract void cry();
}

其具体的实现类为

1
2
3
4
5
6
7
8
class PersonState extends State{
void smile() {
System.out.println("people smile");
}
void cry() {
System.out.println("people cry");
}
}

在Person类中,聚合一个State,调用其相应方法就可以。

1
2
3
4
5
6
7
8
9
public class Person {
State state = new PersonState();
public void smile(){
state.smile();
}
public void cry(){
state.cry();
}
}

有限状态机(FSM),就是不同有限的状态之间的迁移。state design pattens 与 state machine不一样。

线程的一个状态迁移就是有限状态机。

解释器模式Intepreter

动态脚本解析