结构型设计模式
结构型设计模式从程序的结构上解决模块间的耦合,它包括适配器模式、代理模式、装饰模
式、外观模式、桥接模式、组合模式和享元模式。
代理模式
也称为委托模式。安卓进程间通信机制中就使用了这种模式。
定义:为其他对象提供一种代理以控制对这个对象的访问。
下面请出主要角色:
- Subject:抽象主题类声明真实主题与代理的共同接口方法
- RealSubject:真实主题类,代理类代表的真实主题。客户端通过代理类间接调用真实主题类的方法
- Proxy:代理类,持有真实主题类的引用
- Client:客户端类
概述:代理类和真实主题类都实现了抽象主题类,利用多态通过代理类调用真实主题类的方法实现 “代理”。
静态代理简单实现:
静态代理即在代码运行前就已经存在了代理类的class编译文件。
这个例子是我妈托我去商店买东西
1.抽象主题类
public interface IShop {
void buy() {};
}
2.真实主题类,实现类IShop接口和其中的方法
public class Me implements IShop {
@Override
public void buy() {
System.out.println("购买");
}
}
3.代理类
代理类也要实现上面的IShop接口,并持有被代理者IShop的引用,在重写接口的buy方法中调用了被代理者IShop的buy方法
4.客户端类来使用
public class Client {
public static void main(String[] args) {
IShop me = new Me();
IShop purchasing = new Purchasing(me);
purchasing.buy(); //调用真实主题类实现的方法
}
}
动态代理的简单实现:
动态代理即在代码运行时通过反射来动态生成代理类的对象,并且确定到底来代理谁。
动态代理类需要实现动态代理接口InvocationHandler,并重写invoke()方法。
这个例子在上面静态代理的基础上进行修改
1.动态代理类
它实现了InvocationHandler,并重写invoke方法。其中的Object的引用指向被代理类,调用被代理类的具体逻辑在invoke方法中
2.客户端类
下面通过Proxy.newProxyInstance方法来创建动态代理类实例
代理模式的类型:分为动态和静态代理
优点:
- 真实主题类(比如上面的Me类)就是实现实际的业务逻辑,不用关心其他非本职工作。
- 当需求发生变化时,真实主题类就会变化,但是代理类不用做任何修改就能使用。
装饰模式
它在不改变类文件和使用继承的情况下,动态扩展一个对象的功能,是继承的替代方案之一。它通过创建一个包装对象,也就是装饰来包裹真实的对象。
定义:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
装饰模式中有如下角色:
- Component:抽象组件,可以是接口或是抽象类,被装饰的最原始的对象。
- ConcreteComponent:组件具体实现类。Component的具体实现类,被装饰的具体对象。
- Decorator:抽象装饰者,从外类来拓展Component类的功能,但对于Component来说无须知道Decorator的存在。在它的属性中必然有一个private变量指向Component抽象组件。
- ConcreteDecorator:装饰者的具体实现类。
概述:具体组件和抽象装饰者都继承自抽象组件,具体装饰者通过多态拓展传入的具体组件的功能
简单实现
杨过本身就会全真剑法,有两位武学前辈洪七公和欧阳锋分别向杨过传授过打狗棒法和蛤蟆功,这样杨过除了会全真剑法,还会打狗棒法和蛤蟆功。洪七公和欧阳锋就起到了“装饰”杨过的作用。
1.抽象组件
作为武侠,肯定要会使用武功。我们先定义一个武侠的抽象类,里面有使用武功的抽象方法:
2.组件具体实现类
被装饰的具体对象,在这里就是被教授武学的具体武侠,也就是杨过。杨过作为武侠当然也会武学
3.抽象装饰者
抽象装饰者保持了一个对抽象组件的引用,方便调用被装饰对象中的方法。在这个例子中就是武学前辈要持有武侠的引用,方便教授他武学并使他融会贯通:
4.装饰者具体实现类
5.客户端使用
使用场景
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
需要动态地给一个对象增加功能,这些功能可以动态地撤销。
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
优点
通过组合而非继承的方式,动态地扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
有效避免了使用继承的方式扩展对象功能而带来的灵活性差、子类无限制扩张的问题。
具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开放封闭原则”。
缺点
因为所有对象均继承于Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者)。如果基类改变,则势必影响对象的内部。
比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难。对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。所以,只在必要的时候使用装饰模式。
装饰层数不能过多,否则会影响效率。
外观模式
也被称为门面模式。当我们开发Android的时候,无论是做SDK还是封装API,大多都会用到外观模式,它通过一个外观类使得整个系统的结构只有一个统一的高层接口,这样能降低用户的使用成
本。
定义:要求一个子系统的外部与内部的通信必须通过一个统一的对象进行。此模式提供一个高层的
接口,使得子系统更易于使用。
在外观模式中有如下角色。
- Facade:外观类,知道哪些子系统类负责处理请求,将客户端的请求代理给适当的子系统对象。
- Subsystem:子系统类,可以有一个或者多个子系统。实现子系统的功能,处理外观类指派的任务,注意子系统类不含有外观类的引用。
概述:外观类将子系统的逻辑和交互隐藏起来,对外提供一个高层次的接口以供调用
简单实现
这块还举武侠的例子。首先,我们把武侠张无忌当作一个系统。张无忌作为一个武侠,他本身分为3个系统,分别是招式、内功和经脉。
1.子系统类
2。外观类
这里的外观类就是张无忌,他负责将自己的招式、内功和经脉通过不同的情况合理地运用,代码如下所示:
初始化外观类的同时将各个子系统类创建好。很明显,张无忌很好地将自身的各个系统进行了搭配。
如果其使用七伤拳,就需要开启经脉、使用内功九阳神功,接下来使用招式七伤拳;如果其不开启经脉或者不使用九阳神功,那么七伤拳的威力会大打折扣。
3.客户端调用
外观模式本身就是将子系统的逻辑和交互隐藏起来,为用户提供一个高层次的接口,使得系统更加易用,同时也隐藏了具体的实现。这样即使具体的子系统发生了变化,用户也不会感知到。
使用场景:
构建一个有层次结构的子系统时,使用外观模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,则可以让其通过外观接口进行通信,减少子系统之间的依赖关系。
子系统往往会因为不断地重构演化而变得越来越复杂,大多数的模式使用时也会产生很多很小的类,这给外部调用它们的用户程序带来了使用上的困难。我们可以使用外观类提供一个简单的接口,对外隐藏子系统的具体实现并隔离变化。
当维护一个遗留的大型系统时,可能这个系统已经非常难以维护和拓展;但因为它含有重要的功能,所以新的需求必须依赖于它,这时可以使用外观类,为设计粗糙或者复杂的遗留代码提供一个简单的接口,让新系统和外观类交互,而外观类负责与遗留的代码进行交互。
优点:
减少系统的相互依赖,所有的依赖都是对外观类的依赖,与子系统无关。
对用户隐藏了子系统的具体实现,减少用户对子系统的耦合。这样即使具体的子系统发生了变化,用户也不会感知到。加强了安全性,子系统中的方法如果不在外观类中开通,就无法访问到子系统中的方法。
缺点:不符合开放封闭原则。如果业务出现变更,则可能要直接修改外观类。
享元模式
享元模式是池技术的重要实现方式,它可以减少应用程序创建的对象,降低程序内存的占用,提高程序的性能。
定义:使用共享对象有效地支持大量细粒度的对象
要求细粒度对象,那么不可避免地使得对象数量多且性质相近。这些对象分为两个部分:内部状态和外部状态。内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;而外部状态是对象依赖的一个标记,它是随环境改变而改变的并且不可共享的状态。
在享元模式中有如下角色。
- Flyweight:抽象享元角色,同时定义出对象的外部状态和内部状态的接口或者实现。
- ConcreteFlyweight:具体享元角色,实现抽象享元角色定义的业务。
- FlyweightFactory:享元工厂,负责管理对象池和创建享元对象。
概述:将享元角色进行抽象,具体享元实现特定功能,享元工厂负责管理和创建享元对象
简单实现
如果短时间内生成很多对象,就极易产生“Out Of Memory”。因此,我们采用享元模式来对商品的创建进行优化。
1.抽象享元角色
抽象享元角色是一个商品接口,它定义了showGoodsPrice方法来展示商品的价格:
2.具体享元角色
定义类Goods,它实现IGoods 接口,并实现了showGoodsPrice方法,如下所示:
其中name为内部状态,version为外部状态。showGoodsPrice方法根据version的不同会打印出不同的价格。
3.享元工厂
享元工厂GoodsFactory 用来创建Goods对象。通过Map容器来存储Goods对象,将内部状态name作为Map的key,以便标识Goods对象。如果Map容器中包含此key,则使用Map容器中存储的Goods对象;否则就新创建Goods对象,并放入Map容器中。
4.客户端调用
在客户端中调用 GoodsFactory 的 getGoods 方法来创建 Goods 对象,并调用 Goods 的showGoodsPrice方法来显示产品的价格,如下所示:
运行结果如下:
创建商品,key为:iphone7 价格为5199元
使用缓存,key为:iphone7 价格为5199元
使用缓存,key为:iphone7 价格为5999元
从输出可以看出,只有第一次是创建Goods对象,后面因为key值(name字段)相同,所以均使用了对象池中的Goods对象。在这个例子中,name作为内部状态是不变的,并且作为Map的key值是可以共享的。而showGoodsPrice 方法中需要传入的 version值则是外部状态,它的值是变化的。
使用场景
- 系统中存在大量的相似对象。
- 需要缓冲池的场景。
参考资料
《Android进阶之光》