设计模式之结构型模式

结构型模式是用来设计程序的结构的。结构型模式就像搭积木,将不同的类结合在一起形成契合的结构。包括以下几种:

适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式

适配器模式

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

类适配器
被适配的类

public class Adaptee {
    public void adapteeRequest() {
        System.out.println("被适配者的方法");
    }
}

目标接口

public interface Target {
    void request();
}

怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?
如果直接实现 Target 是不行的
但通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可实现

public class ConcreteTarget implements Target {
    @Override
    public void request() {
        System.out.println("concreteTarget目标方法");
    }
}

改:
public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {
        //...一些操作...
        super.adapteeRequest();
        //...一些操作...
    }
}


类图如下:
在这里插入图片描述
对象适配器
对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter 类即可将转变为对象适配器

public class Adapter implements Target{
    // 适配者是对象适配器的一个属性
    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        //...
        adaptee.adapteeRequest();
        //...
    }
}

注意这里的 Adapter 是将 Adaptee 作为一个成员属性,而不是继承它

案例
比如我们现在有一个人力资源管理系统,其中人员信息管理的对象是所有员工的所有信息,这里的所有员工是指在职的员工,其他的离职的,退修的暂时不考虑。类图与代码如下:
在这里插入图片描述

public interface IUserInfo {
	
	//获得用户姓名
	public String getUserName();
	
	//获得家庭地址
	public String getHomeAddress();
	
	//手机号码,这个太重要,手机泛滥呀
	public String getMobileNumber();
	
	//办公电话,一般式座机
	public String getOfficeTelNumber();
	
	//这个人的职位是啥
	public String getJobPosition();
	
	//获得家庭电话,这个有点缺德,我是不喜欢打家庭电话讨论工作
	public String getHomeTelNumber();
}
public class UserInfo implements IUserInfo {
 
	/* 
	 * 获得家庭地址,下属送礼也可以找到地方
	 */
	public String getHomeAddress() {
		System.out.println("这里是员工的家庭地址....");
		return null;
	}
 
	/* 
	 * 获得家庭电话号码
	 */
	public String getHomeTelNumber() {
		System.out.println("员工的家庭电话是....");
		return null;
	}
 
	/* 
	 * 员工的职位,是部门经理还是小兵
	 */
	public String getJobPosition() {
		System.out.println("这个人的职位是BOSS....");
		return null;
	}
 
	/* 
	 * 手机号码
	 */
	public String getMobileNumber() {
		System.out.println("这个人的手机号码是0000....");
		return null;
	}
 
	/* 
	 * 办公室电话,烦躁的时候最好“不小心”把电话线踢掉,我经常这么干,对己对人都有好处
	 */
	public String getOfficeTelNumber() {
		System.out.println("办公室电话是....");
		return null;
	}
 
	/* 
	 * 姓名了,这个老重要了
	 */
	public String getUserName() {
		System.out.println("姓名叫做...");
		return null;
	}
}

但是过了几年,人力资源说需要增加一个功能:借用人员管理。

借用人员虽然在我们公司干活,但是他们的人员信息,工资请款等都是由劳动服务公司管理,并且有一套自己的人员管理系统。所以我们需要将人资系统同步劳动服务公司的信息

劳动服务公司是把人员信息分为三部分:基本信息,办公信息和个人家庭信息,并且都放在HashMap中,类图和代码如下所示:
在这里插入图片描述

@SuppressWarnings("all")
public interface IOuterUser {
	
	//基本信息,比如名称,性别,手机号码了等
	public Map getUserBaseInfo();
	
	//工作区域信息
	public Map getUserOfficeInfo();
	
	//用户的家庭信息
	public Map getUserHomeInfo();
	
}
@SuppressWarnings("all")
public class OuterUser implements IOuterUser {
 
	/* 
	 * 用户的基本信息
	 */
	public Map getUserBaseInfo() {
		HashMap baseInfoMap = new HashMap();
		
		baseInfoMap.put("userName", "这个员工叫混世魔王....");
		baseInfoMap.put("mobileNumber", "这个员工电话是....");
		
		return baseInfoMap;
	}
 
	/* 
	 * 员工的家庭信息
	 */
	public Map getUserHomeInfo() {
		HashMap homeInfo = new HashMap();
		
		homeInfo.put("homeTelNumbner", "员工的家庭电话是....");
		homeInfo.put("homeAddress", "员工的家庭地址是....");
		
		return homeInfo;
	}
 
	/* 
	 * 员工的工作信息,比如职位了等
	 */
	public Map getUserOfficeInfo() {
		HashMap officeInfo = new HashMap();
		
		officeInfo.put("jobPosition","这个人的职位是BOSS...");
		officeInfo.put("officeTelNumber", "员工的办公电话是....");
		
		return officeInfo;
	}
 
}

现在问题来了,咱们的系统要和他们的系统进行交互,怎么办?我们不可能为了这一小小的功能而对我们已经运行良好的系统大动手术,那怎么办?我们可以转化,先拿到对方的数据对象,然后转化为我们自己的数据对象,中间加一层转换处理:按照这个思路,我们可以设计如下类图:
在这里插入图片描述
OuterUserInfo可以看做是“两面派”,实现了IUserInfo接口,还继承了OuterUser,通过这样的设计,把OuterUser伪装成我们系统中一个IUserInfo对象,这样,我们系统基本不用修改,所有的人员查询,调用和本地一样。(我们之所以能够增加一个OuterUserInfo中转类,是因为我们再系统设计时严格遵守了依赖倒置原则和里式 替换原则,否则即使增加中转类也无法解决问题)

中转角色OuterUserInfo代码清单:

@SuppressWarnings("all")
public class OuterUserInfo extends OuterUser implements IUserInfo {
	
	private Map baseInfo = super.getUserBaseInfo();  //员工的基本信息
	private Map homeInfo = super.getUserHomeInfo(); //员工的家庭 信息
	private Map officeInfo = super.getUserOfficeInfo(); //工作信息
	
	/* 
	 * 家庭地址
	 */
	public String getHomeAddress() {
		String homeAddress = (String)this.homeInfo.get("homeAddress");
		System.out.println(homeAddress);
		return homeAddress;
	}
 
	/* 
	 * 家庭电话号码
	 */
	public String getHomeTelNumber() {
		String homeTelNumber = (String)this.homeInfo.get("homeTelNumber");
		System.out.println(homeTelNumber);
		return homeTelNumber;
	}
 
	/* 
	 *职位信息
	 */
	public String getJobPosition() {
		String jobPosition = (String)this.officeInfo.get("jobPosition");
		System.out.println(jobPosition);
		return jobPosition;
	}
 
	/* 
	 * 手机号码
	 */
	public String getMobileNumber() {
		String mobileNumber = (String)this.baseInfo.get("mobileNumber");
		System.out.println(mobileNumber);
		return mobileNumber;
	}
 
	/* 
	 * 办公电话
	 */
	public String getOfficeTelNumber() {
		String officeTelNumber = (String)this.officeInfo.get("officeTelNumber");
		System.out.println(officeTelNumber);
		return officeTelNumber;
	}
 
	/* 
	 * 员工的名称
	 */
	public String getUserName() {
		String userName = (String)this.baseInfo.get("userName");
		System.out.println(userName);
		return userName;
	}
 
}

源码分析适配器模式的典型应用
spring AOP中的适配器模式
在Spring的Aop中,使用的 Advice(通知) 来增强被代理类的功能。

Advice的类型有:MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice

在每个类型 Advice 都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor

Spring需要将每个 Advice 都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换

三个适配者类 Adaptee 如下:

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
}

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;
}

public interface ThrowsAdvice extends AfterAdvice {
}

目标接口 Target,有两个方法,一个判断 Advice 类型是否匹配,一个是工厂方法,创建对应类型的 Advice 对应的拦截器

public interface AdvisorAdapter {
    boolean supportsAdvice(Advice var1);

    MethodInterceptor getInterceptor(Advisor var1);
}

三个适配器类 Adapter 分别如下,注意其中的 Advice、Adapter、Interceptor之间的对应关系

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}
}

@SuppressWarnings("serial")
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof AfterReturningAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}
}

class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		return new ThrowsAdviceInterceptor(advisor.getAdvice());
	}
}

这里看 while 循环里,逐个取出注册的适配器,调用 supportsAdvice() 方法来判断 Advice 对应的类型,然后调用 getInterceptor() 创建对应类型的拦截器

在这里插入图片描述
这里应该属于对象适配器模式,关键字 instanceof 可看成是 Advice 的方法,不过这里的 Advice 对象是从外部传进来,而不是成员属性

spring MVC中的适配器模式
Spring MVC中的适配器模式主要用于执行目标 Controller 中的请求处理方法。

在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ... 
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  

这样假设如果我们增加一个 HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController),这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

我们来看看源码,首先是适配器接口 HandlerAdapter

public interface HandlerAdapter {
    boolean supports(Object var1);

    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

现该接口的适配器每一个 Controller 都有一个适配器与之对应,这样的话,每自定义一个 Controller 需要定义一个实现 HandlerAdapter 的适配器。

springmvc 中提供的 Controller 实现类有如下
在这里插入图片描述

springmvc 中提供的 HandlerAdapter 实现类如下
在这里插入图片描述
其中以一个例子:
HttpRequestHandlerAdapter 这个适配器代码如下

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public HttpRequestHandlerAdapter() {
    }

    public boolean supports(Object handler) {
        return handler instanceof HttpRequestHandler;
    }

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ((HttpRequestHandler)handler).handleRequest(request, response);
        return null;
    }

    public long getLastModified(HttpServletRequest request, Object handler) {
        return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;
    }
}

当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet 会通过 handler 的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 hanle() 方法来调用 Controller 中的用于处理请求的方法。

public class DispatcherServlet extends FrameworkServlet {
    private List<HandlerAdapter> handlerAdapters;
    
    //初始化handlerAdapters
    private void initHandlerAdapters(ApplicationContext context) {
        //..省略...
    }
    
    // 遍历所有的 HandlerAdapters,通过 supports 判断找到匹配的适配器
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
	}
	
	// 分发请求,请求需要找到匹配的适配器来处理
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;

		// Determine handler for the current request.
		mappedHandler = getHandler(processedRequest);
			
		// 确定当前请求的匹配的适配器.
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		ha.getLastModified(request, mappedHandler.getHandler());
					
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    }
	// ...省略...
}	

通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理,免去了写大量的 if-else 语句对 Controller 进行判断,也更利于扩展新的 Controller 类型。

桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

俩个点:
a、定义一个桥接口,使其与一方绑定,这一方的扩展全部使用实现桥接口的方式。
b、定义一个抽象类,来表示另一方,在这个抽象类内部要引入桥接口,而这一方的扩展全部使用继承该抽象类的方式。

实例准备:我们假设有一座桥,桥左边为A,桥右边为B,A有A1,A2,A3等,表示桥左边的三个不同地方,B有B1,B2,B3等,表示桥右边的三个不同地方,假设我们要从桥左侧A出发到桥的右侧B,我们可以有多重方案,A1到B1,A1到B2,A1到B3,A2到B1…等等,以此为例,代码如下:

桥接口:Qiao

1 public interface Qiao {
2     //目的地B
3     void targetAreaB();
4 }

目的地B1,B2,B3:(a注意点)

/**
 * 目的地B1
 */
public class AreaB1 implements Qiao {

    @Override
    public void targetAreaB() {
        System.out.println("我要去B1");
    }

}

/**
 * 目的地B2
 */
public class AreaB2 implements Qiao {

    @Override
    public void targetAreaB() {
        System.out.println("我要去B2");
    }

}

/**
 * 目的地B3
 */
public class AreaB3 implements Qiao {

    @Override
    public void targetAreaB() {
        System.out.println("我要去B3");
    }

}

抽象来源地A:AreaA

public abstract class AreaA {
    //引用桥接口
    Qiao qiao;
    //来源地
    abstract void fromAreaA();
}

来源地A1,A2,A3:

/**
 * 来源地A1
 */
public class AreaA1 extends AreaA {

    @Override
    void fromAreaA() {
        System.out.println("我来自A1");
    }

}

/**
 * 来源地A2
 */
public class AreaA2 extends AreaA {

    @Override
    void fromAreaA() {
        System.out.println("我来自A2");
    }

}

/**
 * 来源地A3
 */
public class AreaA3 extends AreaA {

    @Override
    void fromAreaA() {
        System.out.println("我来自A3");
    }

}

测试类:

public class Clienter {
    public static void main(String[] args) {
        AreaA a = new AreaA2();
        a.qiao = new AreaB3();
        a.fromAreaA();
        a.qiao.targetAreaB();
    }
}

理解:此处抽象化与实现化分别指代实例中的双方,而且实现化对应目的地方(通过实现桥接口进行扩展),抽象方对应来源地方(通过继承抽象类来进行扩展),如果我们不使用桥接模式,我们会怎么想实现这个实例呢?很简单,我们分别定义来源地A1、A2、A3类和目的地B1、B2、B3,然后具体的实现就是,A1到B1一个类,A1到B2一个类,等,如果我们要扩展了A和B ,要直接增加An类和Bn类,如此编写不说类内部重复性代码多,而且还会导致类结构的急剧膨胀,最重要的是,在通过继承实现路径的时候,会造成双方耦合性增大,而这又进一步加剧了扩展的复杂性。使用桥结构模式可以很好地规避这些问题:重在解耦。

JDK提供的JDBC数据库访问接口API正是经典的桥接模式的实现者,接口内部可以通过实现接口来扩展针对不同数据库的具体实现来进行扩展,而对外的仅仅只是一个统一的接口调用,调用方过于抽象,可以将其看做每一个JDBC调用程序(这是真实实物,当然不存在抽象)

组合模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值