结构型模式
结构型模式主要是用于处理类或者对象的组合,它描述了如何来类或者对象更好的组合起来,是从程序的结构上来解决模块之间的耦合问题。它主要包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式这个七个模式。
五、适配器模式
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
5.1 前言
接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化,都需要程序员去重新修改代码。
-
iPhone,你可以使用USB接口连接电脑来充电,假如只有iPhone没有电脑,怎么办呢?苹果提供了iPhone电源适配器。可以使用这个电源适配器充电。这个iPhone的电源适配器就是类似我们说的适配器模式。(电源适配器就是把电源变成需要的电压,也就是适配器的作用是使得一个东西适合另外一个东西。)
-
最典型的例子就是很多功能手机,每一种机型都自带有充电器,有一天自带充电器坏了,而且市场没有这类型充电器可买了。怎么办?万能充电器就可以解决。这个万能充电器就是适配器。
-
当一个第三方库的API改变将会发生什么。过去你只能是咬紧牙关修改所有的客户代码,而情况往往还不那么简单。你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好。你将无法证明这些新特性的利用价值,如果这次升级意味着将要涉及到其它应用程序的客户代码。
适配器(Adapter)模式为对象提供了一种完全不同的接口。可以运用适配器(Adapter)来实现一个不同的类的常见接口,同时避免了因升级和拆解客户代码所引起的纠纷。
5.2 概览
适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配或者不兼容而无法在一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装器模式(把已有的一些类包装起来,使之能有满足需要的接口)。
5.2.1 类图
- 目标角色(Target):定义Client使用的与特定领域相关的接口。用户所期待的新接口。
- 客户角色(Client):与符合Target接口的对象协同。
- 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。需要适配的旧代码。
- 适配器角色(Adapter) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配。将原接口转换为目标接口。
5.2.2 适用场景
- 系统需要使用现有的类,而这些类的接口不符合系统的接口。
- 想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
- 两个类所做的事情相同或相似,但是具有不同接口的时候。
- 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
- 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
5.3 实现
该模式的核心是:Adapter适配器首先要实现目标角色的接口,并且在Adapter适配器中包含一个Adaptee被适配角色的对象,然后在目标接口角色中的旧方法中调用新接口的新方法。
// 目标角色:用户想使用的接口
public interface Target {
/**
* 这个方法将来有可能继续改进
*/
public void hello();
/**
* 该方法不变
*/
public void world();
}
// Adapter:适配器类
public class Adapter implements Target {
// 关联一个被适配的新接口
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
// 实现新老接口的适配转换
@Override
public void hello() {
adaptee.greet();
}
@Override
public void world() {
adaptee.world();
}
}
// 被适配角色:添加了新方法的角色,要将其转换为旧接口
public class Adaptee {
/**
* 加入新的方法
*/
public void greet(){
System.out.println("'Greet '");;
}
/**
* 源类含有的方法
*/
public void world(){
System.out.println("'world'");;
}
}
// 客户端
public class Client {
public static void main(String[] args) throws IOException {
// TargetInterface target = new Adapter();
// target.hello();
// target.world();
Adaptee adaptee = new Adaptee();
Adapter adapter = new Adapter(adaptee);
adapter.hello();
adapter.world();
}
}
5.4 效果
实际上,共有两种适配器模式:类适配器和对象适配器。
类适配器和对象适配器有不同的权衡(此部分内容是《GOF设计模式》)
-
类适配器
- 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
- 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
- 仅仅引入了一个对象,并不需要额外的指针以间接得到 Adaptee。
-
对象适配器则
- 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)—同时工作。Adapter也可以一次给所有的Adaptee添加功能。
- 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
使用Adapter模式时需要考虑的其他一些因素有:
(1)Adapter的匹配程度
对Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名 )到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度
(2)可插入的Adapter
当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,而这些系统对这个类的接口可能会有所不同。
(3)使用双向适配器提供透明操作
使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容 Adaptee的接口,因此并不是所有 Adaptee对象可以被使用的地方它都可以被使用。双向适配器提供了这样的透明性。在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。
5.5 优点
- 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
- 复用了现存的类,解决了现存类和复用环境要求不一致的问题。
- 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
- 一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
六、代理模式
代理模式: 为其他真实的对象提供一种代理,以便更好地控制对这个对象的访问。它可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或添加客户需要的额外服务。
6.1 前言
因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗 ( if和else就是不同的两条逻辑路径) ? 你有想过限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户?以上两种需求都非常类似,并且都需要解决一个更大的问题:你如何提供一致的接口给某个对象让它可以改变其内部功能,或者是从来不存在的功能? 可以通过引入一个新的对象,来实现对真实对象的操作或者将新的对象作为真实对象的一个替身。即代理对象。
例子1:经典例子就是网络代理,你想访问Facebook或者twitter ,如何绕过GFW,找个代理网站。
例子2:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。
6.2 概览
6.2.1 类图
-
抽象主题角色(Subject):
定义真实主题角色RealSubject和抽象主题角色Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。是客户端使用的现有接口。
-
真实对象(RealSubject):
定义了代理角色(proxy)所代表的具体对象。是真实对象的类。
-
代理对象(Proxy):
代理对象通过持有真实主题RealSubject的引用,不但可以控制真实主题RealSubject的创建或删除,可以在真实主题RealSubject被调用前进行拦截,或在调用后进行某些操作。
6.2.2 适用场景
- 在不直接操作对象的情况下,实现对对象的访问
- 中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
6.3 实现
6.3.1 静态代理
/**
* 抽象主题角色
* 定义真实对象与代理对象共同的接口
*/
public interface Subject {
void buyHouse();
void buyBed();
}
/**
* 真实对象
* 定义了真实对象的主操作
*/
public class RealSubject implements Subject {
@Override
public void buyHouse() {
System.out.println("买房.......");
}
@Override
public void buyBed() {
System.out.println("买床.......");
}
}
/**
* 代理对象
* 此为静态代理模式
* 关联了一个真实对象,在实现抽象角色中的方法时除了会调用真实对象外,还可以添加额外的操作
*/
public class ProxySubject implements Subject {
// 关联一个真实对象
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
// 实现Subject中的方法
@Override
public void buyHouse() {
// 预处理
preAddress("房子");
// 调用真实对象的方法
realSubject.buyHouse();
// 后处理
afterAddress("房子");
}
@Override
public void buyBed() {
// 预处理
preAddress("床");
// 调用真实对象的方法
realSubject.buyBed();
// 后处理
afterAddress("床");
}
// 额外的方法
//1 预处理方法
private void preAddress(String thing) {
System.out.println("请付"+thing+"钱.....");
}
//2 后处理方法
private void afterAddress(String thing) {
System.out.println("确认签收:"+thing+".....");
}
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Subject proxySubject = new ProxySubject(realSubject);
// 在不访问真实对象的前提下,通过代理对象间接地实现对其的访问
proxySubject.buyHouse();
proxySubject.buyBed();
}
}
6.3.2 动态代理
参见JAVA高级—反射与动态代理
/**
* 动态代理公有接口/抽象接口
*/
public interface Human {
String getName();
void eat(String food);
}
/**
* 被代理类
*/
public class Superman implements Human {
@Override
public String getName() {
System.out.println("超人...");
return "超人";
}
@Override
public void eat(String food) {
System.out.println("超人吃:" + food);