• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

设计模式_23种设计模式详细解析

java 来源:Chill_Lyn_ 13次浏览

文章目录

参考

http://c.biancheng.net/view/1348.html

单例模式 Singleton

定义与特点

一个类只有一个实例,且该类能自行创建这个实例的一种模式。

  • 单例类只有一个实例对象;
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点;

结构与实现

饿汉式单例

该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。

public class Hungry { 
	//私有化构造器,避免外部创建对象
	private Hungry() { };
	//定义一个静态成员属性,初始化实例对象
	private static final Hungry instance=new Hungry();
	/** * 定义一个公共的静态方法,返回唯一的对象时实例 * @return */
	public static Hungry getInstance() { 
		return instance;
	}
}

懒汉式单例(静态内部类实现)

public class Lazy { 
	//私有化构造器,防止外部调用
	private Lazy() { };
	/** * 静态内部类中定义一个外部类对象的成员属性 * 为了解决线程安全问题 * synchronized代码块效率低 * 利用静态内部类在外部类初始化时不加载,调用静态内部类时才初始化 * static属性只加载一次 * @author 83998 * */
	private static class Inner { 
		private static Lazy instance=new Lazy();
	}
	
	public static Lazy getInstance() { 
		return Inner.instance;
	}
}

懒汉式单例(DCL实现)

/** * 单例模式:在多线程环境下,对外仅存在一个对象 * 1.提供私有的静态属性,存储对象的地址 * 2.构造器私有化,避免外部新建对象 * 3.提供公共的静态方法,获取属性 * 懒汉式套路 * @author Chill Lyn * */
public class DoubleCheckedLocking { 
// 1.提供私有的静态属性,存储对象的地址
// volatile避免多线程double check时出现指令重排
	private static volatile DoubleCheckedLocking instance;

// 2.构造器私有化,避免外部新建对象
	private DoubleCheckedLocking() { 

	}

// 3.提供公共的静态方法,获取属性
	public static DoubleCheckedLocking get() { 
// double check
		if (null != instance) { 
			return instance;
		}
		synchronized (DoubleCheckedLocking.class) { 

			if (null == instance) { 
				instance = new DoubleCheckedLocking();
			}
			return instance;
		}
	}
}

应用场景

  • 在特定应用场景中,某类要求只生成一个对象时。比如,每个国家只有一个总统。
  • 对象需要被共享的场景。Web中的配置对象、数据库连接池等。
  • 对象频繁实例化和被销毁的场景。线程池、网络连接池等。

Spring中的单例模式

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) { 
	//首先尝试从单例对象缓存中读取
	Object singletonObject = this.singletonObjects.get(beanName);
	//如果没有,加锁
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { 
		synchronized (this.singletonObjects) { 
			//double check
			singletonObject = this.earlySingletonObjects.get(beanName);
			//如果没有就创建
			if (singletonObject == null && allowEarlyReference) { 
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) { 
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

Tomcat中的单例模式

所有的Filter,Servlet和Listener的实现类。

原型模式Prototype

定义与特点

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。

  • 高效

结构与实现

Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

  • 抽象原型类:规定了具体原型对象必须实现的接口。Clonable接口。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.*;

public class CreateObject { 
    public static void main(String[] args) { 
        //1.直接new一个person对象
        Person p1 = new Person("cl", 18, new Clothes("black"));

        //2. clone()
        try { 
            Person p4= (Person) p1.clone();
            p4.setName("ddd");
            System.out.println(p4.toString());
        } catch (CloneNotSupportedException e) { 
            e.printStackTrace();
        }
    }

}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements Serializable, Cloneable { 

    private String name;
    private transient Integer age;
    private Clothes clothes;

    /** * deep clone; * * @return * @throws CloneNotSupportedException */
    @Override
    protected Object clone() throws CloneNotSupportedException { 
        Person person = (Person) super.clone();
        person.setClothes((Clothes) person.getClothes().clone());
        return person;
    }
}

@Data
@AllArgsConstructor
class Clothes implements Serializable,Cloneable { 
    private String color;

    /** * shallow clone; * * @return * @throws CloneNotSupportedException */
    @Override
    protected Object clone() throws CloneNotSupportedException { 
        return super.clone();
    }
}

应用场景

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 对象的创建过程比较麻烦,但复制比较简单的时候。

工厂方法模式(Factory Method)

定义与特点

定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

被创建的对象称为“产品”,创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”,它不属于 GoF 的 23 种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

结构与实现

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
/** * 抽象工厂 */
public interface PetFactory { 
    Pet getPet();
}

/** * 抽象产品 */
interface Pet { 
    void voice();
}

/** * 具体工厂 */
class DogFactory implements PetFactory { 

    @Override
    public Pet getPet() { 
        return new Dog();
    }
}
/** * 具体工厂 */
class CatFactory implements PetFactory { 

    @Override
    public Pet getPet() { 
        return new Cat();
    }
}
/** * 具体产品 */
class Dog implements Pet { 

    @Override
    public void voice() { 
        System.out.println("wang");
    }
}

/** * 具体产品 */
class Cat implements Pet { 

    @Override
    public void voice() { 
        System.out.println("mew");
    }
}

class Test { 
    public static void main(String[] args) { 
        DogFactory dogFactory = new DogFactory();
        dogFactory.getPet().voice();
        CatFactory catFactory = new CatFactory();
        catFactory.getPet().voice();
    }
}

应用场景

  • 客户只知道创建产品的工厂名,而不知道具体的产品名。
  • 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  • 客户不关心创建产品的细节,只关心产品的品牌。

Spring中的工厂方法模式

FactoryBean相当于抽象工厂

public interface FactoryBean<T> { 
	@Nullable
	T getObject() throws Exception;
}

FactoryBean的实现类相当于具体工厂

JDK中的工厂方法模式

• java.lang.Object#toString() (在其子类中可以覆盖该方法)
• java.lang.Class#newInstance()
• java.lang.Integer#valueOf(String) (Boolean, Byte, Character,Short, Long, Float 和 Double与之类似)
• java.lang.Class#forName()
• java.lang.reflect.Array#newInstance()
• java.lang.reflect.Constructor#newInstance()

Tomcat中的工厂方法模式

Tomcat在启动的过程中关于SeverSocket的生成使用的是工厂生成的而不是自己new出来的。实际上这些socket就是后期tomcat接收请求时的socket。

serverSocket = serverSocketFactory.createSocket(getPort(),
                        getBacklog());

Tomcat在处理请求的时候,当请求到达StandardWrapperValveinvoke()方法中,在调用指定的servelt实例之前,先创建对应servlet的filterChain,代码如下:

//获取工厂实例
 ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance();
//使用工厂实例创建 filterChain实例
 ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper, servlet);

扩展-简单工厂模式

当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。

public class PetFactory { 

    public static Pet getInstance(String className){ 
        Pet pet=null;
        try { 
            pet= (Pet) Class.forName(className).newInstance();
        } catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) { 
            e.printStackTrace();
        }
        return pet;
    }
}


interface Pet { 
    void voice();
}

class Cat implements Pet { 
    @Override
    public void voice() { 
        System.out.println("mew");
    }
}

class Dog implements Pet { 
    @Override
    public void voice() { 
        System.out.println("wang");
    }
}

Spring中的简单工厂模式

Spring中BeanFactory相当于抽象工厂,getBean(String name)根据名字来获得bean对象,相当于简单工厂模式

抽象工厂模式(AbstractFactory)

定义和特点

工厂方法模式中考虑的是一类产品的生产。同种类称为同等级,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品。

抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

  • 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
  • 系统一次只可能消费其中某一族产品,即同族的产品一起使用。

抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 当增加一个新的产品族时不需要修改原代码,满足开闭原则。

其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改

结构与实现

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系
import lombok.extern.java.Log;

/** * 抽象工厂 */
public interface Factory { 

    Phone createPhone();

    PC createPC();
}

/** * 具体工厂1 */
class MiFactory implements Factory{ 

    @Override
    public Phone createPhone() { 
        return new MiPhone();
    }

    @Override
    public PC createPC() { 
        return new MiBook();
    }
}
/** * 具体工厂2 */
class AppleFactory implements Factory{ 

    @Override
    public Phone createPhone() { 
        return new Iphone();
    }

    @Override
    public PC createPC() { 
        return new MacBook();
    }
}

/** * 抽象产品1 */
interface Phone { 

    void info();
}

/** * 具体产品1 */
@Log
class MiPhone implements Phone{ 

    @Override
    public void info() { 
        log.info("MI 10");
    }
}
/** * 具体产品2 */
@Log
class Iphone implements Phone{ 

    @Override
    public void info() { 
        log.info("iphoneX");
    }
}
/** * 抽象产品2 */
interface PC { 

    void info();
}

/** * 具体产品3 */
@Log
class MiBook implements PC{ 

    @Override
    public void info() { 
        log.info("MiBook air");
    }
}
/** * 具体产品4 */
@Log
class MacBook implements PC{ 

    @Override
    public void info() { 
        log.info("MacBook pro");
    }
}

class Test2{ 
    public static void main(String[] args) { 
        MiFactory miFactory=new MiFactory();
        miFactory.createPC().info();
        AppleFactory appleFactory=new AppleFactory();
        appleFactory.createPhone().info();
    }
}

应用场景

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

建造者模式(Builder)

定义与特点

将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

  • 各个具体的建造者相互独立,有利于系统的扩展。

  • 客户端不必知道产品内部组成的细节,便于控制细节风险。

  • 产品的组成部分必须相同,这限制了其使用范围。

  • 如果产品的内部变化复杂,该模式会增加很多的建造者类。

建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

结构与实现

  • 产品(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。
  • 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  • 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  • 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.java.Log;

@Log
public class TestBuilder { 

    public static void main(String[] args) { 
        Director director=new Director(new MacBuilder());
        log.info(director.build().getCpu().getInfo());
    }
}

/** * 产品 */
@Data
@NoArgsConstructor
@AllArgsConstructor
class PC{ 
    private Cpu cpu;
    private Disk disk;
}

/** * 组件 */
@Data
@NoArgsConstructor
@AllArgsConstructor
class Cpu { 
    private String info;
}
/** * 组件 */
@Data
@NoArgsConstructor
@AllArgsConstructor
class Disk{ 
    private String info;
}

/** * 抽象建造者 */
@Data
@NoArgsConstructor
@AllArgsConstructor
abstract class PCBuilder{ 

    protected PC pc=new PC();
    public abstract void buildCpu();
    public abstract void buildDisk();
    public PC build(){ 
        return pc;
    }
}

/** * 具体建造者 */

class MacBuilder extends PCBuilder{ 

    @Override
    public void buildCpu() { 
        pc.setCpu(new Cpu("A13"));
    }

    @Override
    public void buildDisk() { 
        pc.setDisk(new Disk("Toshiba"));
    }
}

/** * 指挥者 */
@Data
@NoArgsConstructor
@AllArgsConstructor
class Director{ 
    private PCBuilder pcBuilder;

    public PC build(){ 
        pcBuilder.buildCpu();
        pcBuilder.buildDisk();
        return pcBuilder.build();
    }
}

应用场景

建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

Spring中的Builder模式

UriComponent类中有很多属性

通过UriComponentBuilder这个类构建

JDK中的Builder

• java.lang.StringBuilder#append()

• java.lang.StringBuffer#append()

• java.nio.ByteBuffer#put() (CharBuffer, ShortBuffer, IntBuffer,LongBuffer, FloatBuffer 和DoubleBuffer与之类似)

• java.sql.PreparedStatement

• java.lang.Appendable的所有实现类

代理模式(Proxy)

定义与特点

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

  • 代理对象可以扩展目标对象的功能;

  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;

  • 增加了系统的复杂度;

结构与实现

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
import lombok.extern.java.Log;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TestProxy { 
    public static void main(String[] args) { 
        Proxy1 proxy1=new Proxy1();
        proxy1.compute();
        Add add=new Add();
        Compute compute= (Compute) DynamicProxy1.getProxyInstance(add);
        compute.compute();
    }
}

/** * 抽象主题 */
interface Compute { 
    void compute();
}

/** * 真实主题 */
@Log
class Add implements Compute{ 

    @Override
    public void compute() { 
        log.info("1+1=2");
    }
}

/** * 代理类 */
@Log
class Proxy1 implements Compute{ 

    private Add add;

    @Override
    public void compute() { 
        add=new Add();
        log.info("starting");
        add.compute();
        log.info("ending");
    }
}

/** * 动态代理类 */
@Log
class DynamicProxy1{ 

    public static Object getProxyInstance(Object target){ 
        ClassLoader classLoader=target.getClass().getClassLoader();
        Class<?>[] interfaces=target.getClass().getInterfaces();
        InvocationHandler invocationHandler=new InvocationHandler() { 
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
                log.info("starting");
                method.invoke(target, args);
                log.info("ending");
                return null;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

应用场景

  • 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  • 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  • 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
  • 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  • 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

Spring中代理模式

JDK动态代理

public Object getProxy(@Nullable ClassLoader classLoader) { 
	if (logger.isTraceEnabled()) { 
		logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
	}
	Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

Cglib动态代理

public Object getProxy(@Nullable ClassLoader classLoader) { 
	if (logger.isTraceEnabled()) { 
		logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
	}

	try { 
		Class<?> rootClass = this.advised.getTargetClass();
		Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

		Class<?> proxySuperClass = rootClass;
		if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { 
			proxySuperClass = rootClass.getSuperclass();
			Class<?>[] additionalInterfaces = rootClass.getInterfaces();
			for (Class<?> additionalInterface : additionalInterfaces) { 
				this.advised.addInterface(additionalInterface);
			}
		}

		// Validate the class, writing log messages as necessary.
		validateClassIfNecessary(proxySuperClass, classLoader);

		// Configure CGLIB Enhancer...
		Enhancer enhancer = createEnhancer();
		if (classLoader != null) { 
			enhancer.setClassLoader(classLoader);
			if (classLoader instanceof SmartClassLoader &&
					((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { 
				enhancer.setUseCache(false);
			}
		}
		enhancer.setSuperclass(proxySuperClass);
		enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

		Callback[] callbacks = getCallbacks(rootClass);
		Class<?>[] types = new Class<?>[callbacks.length];
		for (int x = 0; x < types.length; x++) { 
			types[x] = callbacks[x].getClass();
		}
		// fixedInterceptorMap only populated at this point, after getCallbacks call above
		enhancer.setCallbackFilter(new ProxyCallbackFilter(
				this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
		enhancer.setCallbackTypes(types);

		// Generate the proxy class and create a proxy instance.
		return createProxyClassAndInstance(enhancer, callbacks);
	}
	catch (CodeGenerationException | IllegalArgumentException ex) { 
		throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
				": Common causes of this problem include using a final class or a non-visible class",
				ex);
	}
	catch (Throwable ex) { 
		// TargetSource.getTarget() failed
		throw new AopConfigException("Unexpected AOP exception", ex);
	}
}

适配器模式Adapter

定义与特点

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

  • 客户端通过适配器可以透明地调用目标接口。
  • 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。

其缺点是:对类适配器来说,更换适配器的实现过程比较复杂。

结构与实现

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.java.Log;

/** * 目标接口-电脑 */
interface Computer{ 
    void work();
}

/** * 适配者-鼠标 */
@Log
class Mouse{ 

    public void mouseWork(){ 
        log.info("鼠标工作了");
    }
}

/** * 适配器 */
@Data
@AllArgsConstructor
@NoArgsConstructor
class Adapter implements Computer{ 

    private Mouse mouse;
    @Override
    public void work() { 
        mouse.mouseWork();
    }
}

public class TestAdapt{ 
    public static void main(String[] args) { 
        Computer computer=new Adapter(new Mouse());
        computer.work();
    }
}

应用场景

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

Spring中的Adapter模式

SpringMVC请求处理流程中,HandlerAdapter适配Handler调用真正地处理方法。

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

HandlerAdapter 接口中有三个抽象方法

实现类有以下几种:

实现类中的handle方法则是调用handler的handleRequest方法

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception { 

	((HttpRequestHandler) handler).handleRequest(request, response);
	return null;
}

JDK中的Adapter模式

InputStreamReader implements Reader But constructs with inputStream.

桥接模式Bridge

定义与特点

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

  • 由于抽象与实现分离,所以扩展能力强;

  • 其实现细节对客户透明。

  • 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。

结构与实现

  • 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
  • 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.java.Log;

public class TestBridge { 
    public static void main(String[] args) { 
        DriverInfo driverInfo=new DriverInfo(new MysqlDriver());
        driverInfo.connect();
    }
}

/** * 实现化对象 */
interface Driver{ 
    void connect();
}

/** * 具体实现化对象1 */
@Log
class MysqlDriver implements Driver{ 

    @Override
    public void connect() { 
        log.info("connected by mysql");
    }
}

/** * 具体实现化对象2 */
@Log
class OracleDriver implements Driver{ 

    @Override
    public void connect() { 
        log.info("connected by Oracle");
    }
}

/** * 扩展抽象化对象(省略了抽象化对象) */
@Data
@AllArgsConstructor
@NoArgsConstructor
class DriverInfo{ 
    private Driver driver;

    public void connect(){ 
        driver.connect();
    }
}

应用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

JDK中的Bridge模式

DriverManager中的DriverInfo与Driver

DriverManager中的DriverInfo的属性之一为Driver接口,不同的数据库厂商通过实现Driver接口实现不同数据库的驱动,而程序操作驱动对象时,不用关系数据库是何种实现,只需要操作DriverInfo即可。

logging中的Handler与Formatter

Handler负责日志输出,每个Handler中都会有一个Formatter引用,Formatter负责格式化日志。

装饰器模式Decorator

定义与特点

指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

  • 采用装饰模式扩展对象的功能比采用继承方式更加灵活。

  • 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。

  • 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。

结构与实现

  • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.java.Log;

public class TestDecorator { 
    public static void main(String[] args) { 
        BufferedReader bufferedReader=new BufferedReader(new FileReader());
        bufferedReader.readLine();
    }
}

/** * 抽象构件 */
interface Reader{ 
    void read();
}

/** * 具体构件 */
@Log
class FileReader implements Reader{ 

    @Override
    public void read() { 
        log.info("文件读取");
    }
}

/** * 装饰角色 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Log
class BufferedReader implements Reader{ 

    private  Reader reader;
    @Override
    public void read() { 
        reader.read();
    }

    public void readLine(){ 
        read();
        log.info("并且仅仅读取一行");
    }
}

应用场景

  • 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

IO流中的Decorator

BufferedReader

Spring中的Decorator

Wrapper和Decorator相关的类

Spring Session中的Decorator

spring session核心原理

  • @EnableRedisHttpSession导入RedisHttpSessionConfiguration配置
    • 给容器中添加RedisOperationSessionRepository组件,redis操作session的crud封装类
    • 给容器中添加SessionRepositoryFilter,session存储过滤器,每个请求都经过Filter
      • 创建的时候自动获取到RedisOperationSessionRepository
      • 原生的RequestResponse被包装成SessionRepositoryRequestWrapperSessionRepositoryResponseWrapper
      • request.getSession()->wrapperRequest.getSession()->从RedisOperationSessionRepository中获取

Mybatis二级缓存中的Decorator

装饰器 功能
SynchronizedCache 同步锁,用于保证对指定缓存区的操作都是同步的
LoggingCache 统计器,记录缓存命中率
BlockingCache 阻塞器,基于key加锁,防止缓存穿透
ScheduledCache 时效检查,用于验证缓存有效器,并清除无效数据
LruCache 溢出算法,淘汰闲置最久的缓存
FifoCache 溢出算法,淘汰加入时间最久的缓存
WeakCache 溢出算法,基于java弱引用规则淘汰缓存
SoftCache 溢出算法,基于java软引用规则淘汰缓存
PerpetualCache 实际存储,内部采用HashMap进行存储

每个装饰器都会通过属性引用下一个装饰器,从而组成一个链条。缓存逻辑基于链条进行传递。

外观模式/门面模式Facade

定义与特点

是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。

  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

  • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

  • 不能很好地限制客户使用子系统类。

  • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

结构与实现

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

应用场景

  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

参考:https://blog.csdn.net/wwwdc1012/article/details/82729516

Spring jdbc中的外观模式

查看 org.springframework.jdbc.support.JdbcUtils该工具类主要是对原生的 jdbc 进行了封装

Mybatis中的外观模式

  • MapperProxySqlsession都是与数据库交互的门面,方便调用。
  • 查看 org.apache.ibatis.session.Configuration 类中以 new 开头的方法,该类主要对一些创建对象的操作进行封装

Tomcat 中的外观模式

Tomcat 源码中大量使用了很多外观模式

org.apache.catalina.connector.Requestorg.apache.catalina.connector.RequestFacade 这两个类都实现了 HttpServletRequest 接口

Request 中调用 getRequest() 实际获取的是 RequestFacade 的对象

protected RequestFacade facade = null;

public HttpServletRequest getRequest() { 
    if (facade == null) { 
        facade = new RequestFacade(this);
    }
    return facade;
}

RequestFacade 中再对认为是子系统的操作进行封装

public class RequestFacade implements HttpServletRequest { 
    /** * The wrapped request. */
    protected Request request = null;

    @Override
    public Object getAttribute(String name) { 
        if (request == null) { 
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        }
        return request.getAttribute(name);
    }
    // ...省略...
}    

SLF4j的外观模式

SLF4J 是简单的日志外观模式框架,抽象了各种日志框架例如 Logback、Log4j、Commons-logging 和 JDK 自带的 logging 实现接口。它使得用户可以在部署时使用自己想要的日志框架。

享元模式FlyWeight

定义与实现

运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的又橡来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

  • 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
  • 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
  • 读取享元模式的外部状态会使得运行时间稍微变长。

结构与实现

享元模式中存在以下两种状态:

  • 内部状态,即不会随着环境的改变而改变的可共享部分;
  • 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。下面来分析其基本结构和实现方法。
    享元模式的主要角色有如下。
  • 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  • 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  • 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

应用场景

  • 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  • 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  • 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

JDK类库中的享元模式

包装类中的缓存值都属于享元模式。

public static Integer valueOf(int i) { 
	final int offset = 128;
	if (i >= -128 && i <= 127) {  // must cache 
	    return IntegerCache.cache[i + offset];
	}
        return new Integer(i);
}
public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false);

public static Boolean valueOf(boolean b) { 
    return (b ? TRUE : FALSE);
}
public static Byte valueOf(byte b) { 
	final int offset = 128;
	return ByteCache.cache[(int)b + offset];
}
public static Character valueOf(char c) { 
	if(c <= 127) {  // must cache
	    return CharacterCache.cache[(int)c];
	}
        return new Character(c);
}

Mybatis中的享元模式

ReuseExecutor可重用执行器,底层是维护了一个Map<String sql,Statement stmt> 来捕捉到相同的SQL,则直接取对应缓存的Statement进行执行,所以对于相同SQL(包括queryupdate),不同参数,则只进行一次预编译。就可以复用设置参数来执行。避免了频繁创建和销毁Statement对象,从而提升系统性能,这是享元思想的应用。

组合模式Composite

定义与特点

有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;

  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系;

  • 不容易限制容器中的构件;

  • 不容易用继承的方法来增加构件的新功能;

组成与实现

  • 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
  • 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
  • 树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

(1) 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。

(2) 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。

应用场景

  • 在需要表示一个对象整体与部分的层次结构的场合。
  • 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

Spring中的组合模式

Spring中的Composite相关的类都运用了组合模式

class WebMvcConfigurerComposite implements WebMvcConfigurer { 

	private final List<WebMvcConfigurer> delegates = new ArrayList<>();


	@Override
	public void configurePathMatch(PathMatchConfigurer configurer) { 
		for (WebMvcConfigurer delegate : this.delegates) { 
			delegate.configurePathMatch(configurer);
		}
	}

具体方法通过遍历集合来调用。

模板方法Template Method

定义与特点

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。

  • 它在父类中提取了公共的部分代码,便于代码复用。
    部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。

  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

结构与实现

模板方法模式包含以下主要角色。

(1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

(2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

应用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

JDK中模板方法

  • java.io.InputStream, java.io.OutputStream, java.io.Reader和java.io.Writer的所有非抽象方法
  • java.util.AbstractList, java.util.AbstractSet和java.util.AbstractMap的所有非抽象方法
  • javax.servlet.http.HttpServlet#doXXX()

Spring中模板方法

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");
RestTemplate...

Tomcat中的模板方法

java.servlet.GenericServlet定义了模板方法,同时,javax.servlet.http.HttpServlet提供了默认的模板实现,HttpServlet的子类,不直接实现或者override了service方法,类似于doGetdoPost等等。

Mybatis中的模板方法

BaseExecutor是Executor的基本抽象实现,采用模板设计模式,里边提取了连接维护、一级缓存的公有功能,供子类复用。并放出了doQuery、doUpdate等抽象方法下放到子类做差异实现。

protected abstract int doUpdate(MappedStatement ms, Object parameter)
    throws SQLException;

protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
    throws SQLException;

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
    throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
    throws SQLException;

策略模式 Strategy

定义与特点

该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

  • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。

  • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。

  • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。

  • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。

  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

  • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。

  • 策略模式造成很多的策略类。

结构与实现

  • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

应用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

JDK中的Strategy

Comparator

在Java的集合框架中,经常需要通过构造方法传入一个比较器Comparator,或者创建比较器传入Collections的静态方法中作为方法参数,进行比较排序等,使用的是策略模式。

在该比较架构中,Comparator就是一个抽象的策略;一个类实现该结构,并实现里面的compare方法,该类成为具体策略类;Collections类就是环境角色,他将集合的比较封装成静态方法对外提供api。

ThreadPoolExecutor中的拒绝策略

在创建线程池时,需要传入拒绝策略,当创建新线程使当前运行的线程数超过maximumPoolSize时,将会使用传入的拒绝策略进行处理。

AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。

Spring中Strategy

InstantiationStrategy

/** * Interface responsible for creating instances corresponding to a root bean definition. *抽象策略接口 */
public interface InstantiationStrategy { 
/** * Default object instantiation strategy for use in BeanFactories. *具体策略 */
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy { 
/** * 环境 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory { 

	/** Strategy for creating bean instances. */
	private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

命令模式 Command

定义与特点

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。

  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。

  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。

  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

  • 其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。

结构与实现

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  • 具体命令角色(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.java.Log;

public class TestCommand { 

    public static void main(String[] args) { 
        Guest guest=new Guest(new noodles(new Cooker()));
        guest.order();
    }
}

/** * 抽象命令-菜单 */
interface Menu{ 
    void cook();
}

/** * 接收者-厨师 */
@Log
class Cooker{ 
    public void cook(){ 
        log.info("cooker is cooking");
    }
}

/** * 具体命令-面条 */
@Log
@AllArgsConstructor
@NoArgsConstructor
@Data
class noodles implements Menu{ 

    private Cooker cooker;
    @Override
    public void cook() { 
        log.info("Noodles!");
        cooker.cook();
    }
}

/** * 调用者-顾客 */
@Log
@AllArgsConstructor
@NoArgsConstructor
@Data
class Guest{ 
    private Menu menu;

    public void order(){ 
        menu.cook();
    }
}

应用场景

  • 当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互。
  • 当系统需要随机请求命令或经常增加或删除命令时,命令模式比较方便实现这些功能。
  • 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
  • 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
  • java.lang.Runnable

责任链模式 Chain of Responsibility

定义与特点

为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

责任链模式是一种对象行为型模式,其主要优点如下。

  • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

其主要缺点如下:

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

结构与实现

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

应用场景

  • 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
  • 可动态指定一组对象处理请求,或添加新的处理者。
  • 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

Spring中的责任链

Spring MVC DispatcherServlet中的HandlerExecutionChain

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { 
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) { 
		for (int i = 0; i < interceptors.length; i++) { 
			HandlerInterceptor interceptor = interceptors[i];
			if (!interceptor.preHandle(request, response, this.handler)) { 
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}

JDK中的责任链

public abstract class Filter { 
    protected Filter() { 
    }

    public abstract void doFilter(HttpExchange var1, Filter.Chain var2) throws IOException;

Tomcat中责任链

状态模式 State

定义与特点

对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

  • 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。

  • 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。

  • 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

  • 状态模式的使用必然会增加系统的类与对象的个数。

  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。

结构与实现

  • 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为。

应用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

观察者模式

定义与特点

指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式,其主要优点如下:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 目标与观察者之间建立了一套触发机制。

它的主要缺点如下:

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

结构与实现

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
  • 抽象目标
  • 抽象观察者
import lombok.extern.java.Log;

import java.util.Observable;
import java.util.Observer;

public class TestObserver { 
    public static void main(String[] args) { 
        Teacher teacher=new Teacher();
        Student s1=new Student();
        Student s2=new Student();
        teacher.addObserver(s1);
        teacher.addObserver(s2);
        teacher.speak("class is over!");
    }
}

/** * 具体目标-老师 */
class Teacher extends Observable { 

    public void speak(String msg){ 
        setChanged();
        notifyObservers(msg);
    }
}

/** * 具体观察者- 学生 */
@Log
class Student implements Observer{ 

    /** * This method is called whenever the observed object is changed. An * application calls an <tt>Observable</tt> object's * <code>notifyObservers</code> method to have all the object's * observers notified of the change. * * @param o the observable object. * @param arg an argument passed to the <code>notifyObservers</code> */
    @Override
    public void update(Observable o, Object arg) { 
        log.info("heard "+arg+"from"+o);
    }
}

应用场景

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

JDK中的观察者模式

  • java.util.Observer/java.util.Observable
  • java.util.EventListener (所有子类)
  • javax.servlet.http.HttpSessionBindingListener
  • javax.servlet.http.HttpSessionAttributeListener

Spring中的观察者模式

  • 事件:ApplicationEvent 是所有事件对象的父类。ApplicationEvent 继承自 jdk 的 EventObject, 所有的事件都需要继承 ApplicationEvent, 并且通过 source 得到事件源。

Spring 也为我们提供了很多内置事件,ContextRefreshedEventContextStartedEventContextStoppedEventContextClosedEventRequestHandledEvent

  • 事件监听:ApplicationListener,也就是观察者,继承自 jdk 的 EventListener,该类中只有一个方法 onApplicationEvent。当监听的事件发生后该方法会被执行。

  • 事件源:ApplicationContextApplicationContext 是 Spring 中的核心容器,在事件监听中 ApplicationContext 可以作为事件的发布者,也就是事件源。因为 ApplicationContext 继承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定义了事件发布的方法:publishEvent(Object event)

  • 事件管理:ApplicationEventMulticaster,用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表。

Tomcat中观察者模式

Tomcat的组件生命周期状态只要一变化,Tomcat就会通知改组件的所有的观察者,把状态变化通知到所有的观察者,看是否有观察者对相关组件的状态变化感兴趣。

public void fireLifecycleEvent(String type, Object data) { 

    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
    LifecycleListener interested[] = listeners;
    for (int i = 0; i < interested.length; i++)
        interested[i].lifecycleEvent(event);

}

中介模式 Mediator

定义与特点

定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式是一种对象行为型模式,其主要优点如下。

  • 降低了对象之间的耦合性,使得对象易于独立地被复用。

  • 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

  • 其主要缺点是:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

结构与实现

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

应用场景

  • 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。

  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

  • java.util.Timer (所有scheduleXXX()方法)

  • java.util.concurrent.Executor#execute()

  • java.util.concurrent.ExecutorService (invokeXXX()和submit()方法)

  • java.util.concurrent.ScheduledExecutorService (所有scheduleXXX()方法)

  • java.lang.reflect.Method#invoke()

迭代器模式 Iterator

定义与特点

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下。

  • 访问一个聚合对象的内容而无须暴露它的内部表示。

  • 遍历任务交由迭代器完成,这简化了聚合类。

  • 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。

  • 增加新的聚合类和迭代器类都很方便,无须修改原有代码。

  • 封装性良好,为遍历不同的聚合结构提供一个统一的接口。

  • 其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。

结构与实现

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
package iterator;
import java.util.*;
public class IteratorPattern
{ 
}
//抽象聚合
interface Aggregate
{  
    public void add(Object obj); 
    public void remove(Object obj); 
    public Iterator getIterator(); 
}
//具体聚合
class ConcreteAggregate implements Aggregate
{  
    private List<Object> list=new ArrayList<Object>(); 
    public void add(Object obj)
    {  
        list.add(obj); 
    }
    public void remove(Object obj)
    {  
        list.remove(obj); 
    }
    public Iterator getIterator()
    {  
        return(new ConcreteIterator(list)); 
    }     
}
//抽象迭代器
interface Iterator
{ 
    Object first();
    Object next();
    boolean hasNext();
}
//具体迭代器
class ConcreteIterator implements Iterator
{  
    private List<Object> list=null; 
    private int index=-1; 
    public ConcreteIterator(List<Object> list)
    {  
        this.list=list; 
    } 
    public boolean hasNext()
    {  
        if(index<list.size()-1)
        {  
            return true;
        }
        else
        { 
            return false;
        }
    }
    public Object first()
    { 
        index=0;
        Object obj=list.get(index);;
        return obj;
    }
    public Object next()
    {  
        Object obj=null; 
        if(this.hasNext())
        {  
            obj=list.get(++index); 
        } 
        return obj; 
    }   
}

应用场景

  • 当需要为聚合对象提供多种遍历方式时。

  • 当需要为遍历不同的聚合结构提供一个统一的接口时。

  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

  • java.util.Iterator

  • java.util.Enumeration

访问者模式 Visitor

定义与特点

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

  • 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  • 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下。

  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

结构与实现

  • 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  • 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  • 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  • 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

应用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  • 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

Spring的Visitor模式

public class BeanDefinitionVisitor { 

	@Nullable
	private StringValueResolver valueResolver;


	/** * Create a new BeanDefinitionVisitor, applying the specified * value resolver to all bean metadata values. * @param valueResolver the StringValueResolver to apply */
	public BeanDefinitionVisitor(StringValueResolver valueResolver) { 
		Assert.notNull(valueResolver, "StringValueResolver must not be null");
		this.valueResolver = valueResolver;
	}

	/** * Traverse the given BeanDefinition object and the MutablePropertyValues * and ConstructorArgumentValues contained in them. * @param beanDefinition the BeanDefinition object to traverse * @see #resolveStringValue(String) */
	public void visitBeanDefinition(BeanDefinition beanDefinition) { 
		visitParentName(beanDefinition);
		visitBeanClassName(beanDefinition);
		visitFactoryBeanName(beanDefinition);
		visitFactoryMethodName(beanDefinition);
		visitScope(beanDefinition);
		if (beanDefinition.hasPropertyValues()) { 
			visitPropertyValues(beanDefinition.getPropertyValues());
		}
		if (beanDefinition.hasConstructorArgumentValues()) { 
			ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
			visitIndexedArgumentValues(cas.getIndexedArgumentValues());
			visitGenericArgumentValues(cas.getGenericArgumentValues());
		}
	}

备忘录模式 Memento

定义与特点

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式是一种对象行为型模式,其主要优点如下。

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。

  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。

  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

  • 其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

结构与实现

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

应用场景

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

解释器模式 Interpreter

定义与特点

给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

解释器模式是一种类行为型模式,其主要优点如下。

  • 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  • 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

解释器模式的主要缺点如下。

  • 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  • 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  • 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

结构与实现

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

应用场景

  • 当语言的文法较为简单,且执行效率不是关键问题时。
  • 当问题重复出现,且可以用一种简单的语言来进行表达时。
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。

JDK中的解释器模式

Pattern

Spring中的解释器模式

EpressionParser


版权声明:本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。
喜欢 (0)