结构型模式
结构型模式是为解决怎样组装现有的类,设计他们的交互方式,从而达到实现一定的功能的目的。结构型模式包容了对很多问题的解决。例如:扩展性(外观、组成、代理、装饰)封装性(适配器,桥接)
一、桥接模式
作用:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。桥接模式的核心意图就是把这些实现独立出来,让他们各自变化,让每个实现的变化都不会影响其它实现,从而达到应对变化的目的。
例如:手机即可以按品牌分类又可以功能分类。
按桥接模式的思想,可将手机分为手机品牌和功能软件两种抽象,而手机品牌包含了手机软件弄成聚合关系。
要点:
实现系统可能有多个角度,每一种分类都有可能变化,桥接模式就是把这种多角度分离出来,找出变化点并封装它们,让他们独立地变化,减少他们之间的耦合,桥接模式实际上是一种设计原则的应用。
备注:
在面对变化的时候,继承是个好东西,但往往会过度地使用,继承会导致类的结构过于复杂,关系太多,难以维护且扩展性差。如果发现继承体系中,有两个甚至多个方向的变化,那么就解耦这些不同方向的变化,通过对象组合的方式,把两个角色之间的继承关系改为组合的关系,从而使这两者可以应对各独立的变化。
二、组合模式
作用:
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
要点:
1、声明Tree接口,同时添加add(),remove(),getChildrens()方法及其它共有的方法。
2、继承Tree接口,实现树中的根节点和叶子节点,在根节点中包含其它Tree组合信息。
备注:
当需求中需要体现部分与整体层次结构时或希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时应该考虑用组合模式。
三、享元模式
作用:
运行共享技术有效地支持大量细粒度的对象
要点:
1、声明一个Flyweight接口,需要共享的对象或不需要共享的对象都实现此接口,接口中的operator(Object state)方法用于将变化的外部,当参数传进共享对象中实现功能逻辑。
2、创建FlyweightFactory工厂类,负责Flyweight对象的创建并用Map缓存每一个创建的对象。FlyweightFactory类取对象时,通过key值到缓存Map中取,如果存在则直接返回缓存的对象,如果不存在则新建并返回和缓存。
/**
* 享元工厂类
*/
publicclass FlyweightFactory {
private Map<String,Flyweight> map = new HashMap<String, Flyweight>();
public Flyweight getInstance(String key) {
Flyweight inst = map.get(key);
if(inst==null){
inst = new FlyweightImpl();
map.put(key, inst);
}
return inst;
}
}
/**
* 客户端类
*/
publicclass Client {
publicstaticvoid main(String[] args) {
String state = "外部状态";
FlyweightFactory factory = new FlyweightFactory();
Flyweight one = factory.getInstance("one");
one.operation(state);// 将外部状态传入共享对象去计算
UnshareFlyweightImpl unsh = new UnshareFlyweightImpl();// 不共享的对象
unsh.operation(state);
}
}
备注:
享元模式可以避免大量非常相相似类的开销。如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时,就应该考虑使用享元模式。另外,如果对象的大多数据状态是外部状态,如果删除对象的外部状态,那么可以用相对较小或较少的共享对象取代较大或较多组的对象时,也可以考虑用共享模式。如在程序设计中,有时需要生成大量细粒度的类实例来表示数据。而这些实例中除了几个特别参数外,其它参数基本相同,这时可以将这些不同的参数移到实例的外面,在方法调用时再将他们当参数传进来,这样就可以通过共享公共参数大幅度减少单个实例的数目。
四、装饰模式
作用:
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成更为灵活。
要点:
1、定义接口Component,实现此接口的对象可以动态添加职责。
2、ComObject是待装饰的类,实现Commponent接口。
3、定义装饰抽象类Decorator,继承Commponent接口。在Decorator中包含一个实现了Commponent接口的待装饰对象commponent。
4、覆写Decorator中继承自Commponent接口的方法,将operation方法中调用待装饰类的commponent.operation()方法。
5、继承Dectorator实现不同的装饰类。覆写operation方法,在适当的位置调用super. operation();
客户端调用代码:
publicclass Client {
publicstaticvoid main(String[] args) {
ComObject comobject = new ComObject();
DecoratorImpl dec1 = new DecoratorImpl();
DecoratorImpl2 dec2 = new DecoratorImpl2();
dec1.setComponent(comobject);// 装饰一
dec2.setComponent(dec1); // 装饰二
dec2.operation(); // 最后一个装饰完后显示结果
}
}
备注:
面对变化,如果采用生成子类的方法进行扩充,为支持每一种扩展组合,会产生大量的子类,使得子类的数量呈爆炸性增加,这是继承所带来的灾难,而事实上,这些子类多半只是为某个对象增中一些职责,此时通过装饰的方式,可以更加灵活,以动态、透明的方式给单个对象添加职责,并可在不需要时撤销相应的职责。
装饰模式适用于,当某些功能扩展了原为类的核心职责或主要行为,这些功能并非程序的主体功能,只是在适当的时候才用,则可以考虑用装饰模式去扩展原有类。
装饰模式与建造者模式的区别在于,装饰模式中装饰物与被装饰物继承自同一组件类或接口,组件对象均可由装饰物在外部(客户代码中)按顺序装饰;各装饰物记住自己装饰了谁,其功能函数除了实现自己的功能外,还需要调用被自己装饰的组件的功能函数;装饰模式针对构建过程不稳定的情况。而在建造者模式中,被建造对象集成一个成员组成稳定的接口,并对成员内容做具体实现;被创建对象的成员有一个指挥类按顺序建造各组成部分,指挥类隔离建造过程及客户代码;建造者模式针对建造过程十分稳定的情况。
五、代理模式
作用:
为其它对象提供一种代理以控制对这个对象的访问
要点:
1、定义公共的接口Todo
2、代理类和真实类都实现Todo接口,同时代理类保存一个真实类的引用。
3、覆写的代理类的todo()方法,在其中调用真实类的todo()方法。
备注:
代理模式适用的场景一般分为以下几种:
第一种,远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
第二种,虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
第三种,安全代理,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候
第四种,智能指引,是指当调用真实的对象时,代理处理另外一些事。
六、外观模式
作用:
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得子系统更加容易使用。
要点:
1、定一个Facade外观类,提包含全部的子系统引用
2、在Facade中定义更加便捷的方法,去访问子系统,这个方法可以由多个子系统组合完成。
public class Facade {
private System sysOne; //子系统一
private System2 sysTwo; //子系统二
private System3 sysThree; //子系统三
/**
*新开放的简化接口
*/
public void operationOne() {
sysOne.todoOne();
sysThree.todoThree();
}
/**
* 新开放的简化接口
*/
public void operationTwo() {
sysTwo.todoTwo();
sysThree.todoThree();
}
}
备注:
外观模式的使用可分来三个阶段:
第一,在设计初期阶段,应该有意识的将不同的两个层分离。如MVC,在层与层之间建立外观Facade。
第二,在开发阶段,子系统往往因为不断的重构而变得越来越复杂,产生很多很小的类,增加外观Façade可以提供一个简单的接口,减少它们之间的依赖。
第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了。但因为它包含非常重要的功能,新的需求开发必须依赖于它时,此时,可以为新开发一个外观Façade类,为设计粗糙或高度复杂的遗留代码提供一个比较清晰简单的接口,让新系统与Façade对象交互,Façade与遗留代码交互所有复杂的工作。
外观模式与代理模式的主要区别在于,代理对象代表一个单一对象,而外观对象代表的是一个子系统;代理的客户对象无法直接访问目标对象,只能通过代理对象间接访问,而外观客户对象可以直接访问子系统中的各个对象。但通常由外观对象提供集合了子系统各元件功能的更为简便的方法供客户端对象调用。
七、适合器模式
作用:
解决两个已有的接口之间不匹配的问题,将一个类的接口转换成客户希望的另外一个接口。适配器模式合得原本由接口不兼容而不能一起工作的那些类可以一起工作。
要点:
1、声明一个Need的公共接口
2、类Adapter实现Need接口,同时关联一个Other类的引用,覆写Need接口的todo()方法,在方法中,调用Other对象中的doSomething()方法。
备注:
适配器模式一般用于在软件维护扩展阶段,想使用一个已经存在的类,但他的方法和接口不符合新功能需求时且双方都不太容易修改的时候才使用适配器模式,为客户端代码提供统一调用接口。
适配器模式与代理模式都是属于一种衔接性质的功能。代理是一种原来对象的代表,其它需要与原有对象打交道的操作都和这个代表交涉,而适配器则不需要虚构出一个代表者,只需要为就会特定使用目的,将原来的类进行一些组合。代理模式中的原类和代理类继承同一父类;原类对象与代理类对象接口相同,功能一致,起到了隐藏原类的作用,而适配器模式只有适配器继承目标接口,然后将原有类接口转换为目标代码需求的接口。
适配器模式与外观模式的区别在于外观模式定义的是一个新的接口,而适配器则是利用一个原有的接口,适配器是使两个已经有的接口协同工作,而外观则是为现存系统提供一个更为方便的访问接口。外观针对的对象粒度更大,适配器是用来甜酸对象的,而外观是用来适配整个子系统的。