文章目錄
- 參考
- 單例模式 Singleton
- 原型模式Prototype
- 工廠方法模式(Factory Method)
- 抽象工廠模式(AbstractFactory)
- 建造者模式(Builder)
- 代理模式(Proxy)
- 適配器模式Adapter
- 橋接模式Bridge
- 裝飾器模式Decorator
- 外觀模式Facade
- 享元模式FlyWeight
- 組合模式Composite
- 模板方法Template Method
- 策略模式 Strategy
- 命令模式 Command
- 責任鏈模式 Chain of Responsibility
- 狀態模式 State
- 觀察者模式
- 中介模式 Mediator
- 迭代器模式 Iterator
- 訪問者模式 Visitor
- 備忘錄模式 Memento
- 解釋器模式 Interpreter
參考
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;
}
原型模式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()
擴展-簡單工廠模式
當需要生成的產品不多且不會增加,一個具體工廠類就可以完成任務時,可刪除抽象工廠類。
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相關的類
外觀模式Facade
定義與特點
是一種通過爲多個複雜的子系統提供一個一致的接口,而使這些子系統更加容易被訪問的模式。該模式對外有一個統一接口,外部應用程序不用關心內部子系統的具體的細節,這樣會大大降低應用程序的複雜度,提高了程序的可維護性。
-
降低了子系統與客戶端之間的耦合度,使得子系統的變化不會影響調用它的客戶類。
-
對客戶屏蔽了子系統組件,減少了客戶處理的對象數目,並使得子系統使用起來更加容易。
-
降低了大型軟件系統中的編譯依賴性,簡化了系統在不同平臺之間的移植過程,因爲編譯一個子系統不會影響其他的子系統,也不會影響外觀對象。
-
不能很好地限制客戶使用子系統類。
-
增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
結構與實現
- 外觀(Facade)角色:爲多個子系統對外提供一個共同的接口。
- 子系統(Sub System)角色:實現系統的部分功能,客戶可以通過外觀角色訪問它。
- 客戶(Client)角色:通過一個外觀角色訪問各個子系統的功能。
應用場景
- 對分層結構系統構建時,使用外觀模式定義子系統中每層的入口點可以簡化子系統之間的依賴關係。
- 當一個複雜系統的子系統很多時,外觀模式可以爲系統設計一個簡單的接口供外界訪問。
- 當客戶端與多個子系統之間存在很大的聯繫時,引入外觀模式可將它們分離,從而提高子系統的獨立性和可移植性。
參考:https://blog.csdn.net/wwwdc1012/article/details/82729516
Spring jdbc中的外觀模式
查看 org.springframework.jdbc.support.JdbcUtils
該工具類主要是對原生的 jdbc 進行了封裝
Mybatis中的外觀模式
查看 org.apache.ibatis.session.Configuration
類中以 new
開頭的方法,該類主要對一些創建對象的操作進行封裝
Tomcat 中的外觀模式
Tomcat 源碼中大量使用了很多外觀模式
org.apache.catalina.connector.Request
和 org.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);
}
組合模式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...
策略模式 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;
狀態模式 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 也爲我們提供了很多內置事件,ContextRefreshedEvent
、ContextStartedEvent
、ContextStoppedEvent
、ContextClosedEvent
、RequestHandledEvent
。
-
事件監聽:ApplicationListener,也就是觀察者,繼承自 jdk 的 EventListener,該類中只有一個方法
onApplicationEvent
。當監聽的事件發生後該方法會被執行。 -
事件源:ApplicationContext,
ApplicationContext
是 Spring 中的核心容器,在事件監聽中ApplicationContext
可以作爲事件的發佈者,也就是事件源。因爲ApplicationContext
繼承自ApplicationEventPublisher
。在ApplicationEventPublisher
中定義了事件發佈的方法:publishEvent(Object event)
-
事件管理:
ApplicationEventMulticaster
,用於事件監聽器的註冊和事件的廣播。監聽器的註冊就是通過它來實現的,它的作用是把Applicationcontext
發佈的 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