前言
在Java体系结构与设计模式一书中,是这样描述结构模式的:把责任委托给其他类的对象,从而引入一种耦合度低的分层体系结构。这种结构带来的好处就是(1)方便对象间的通信,比如,当某个对象以通常的方式无法访问,或因接口不兼容导致某个对象不可用时。(2)另外,结构模式提供了组织聚合对象的方式,从而使其能完整地被创建;并且结构模式还提供了及时回收系统资源的方式。
装饰器
装饰器将目标对象拓展的功能封装在另一个一个类中,接着,客户端的请求转发到目标对象之前,先经过装饰器,然后,装饰器再将这些请求转给目标对象(之前或之后)时,动态的将拓展的功能加入目标对象。这种动态地扩展对象功能的方式,不需要我们改变原始的代码或使用继承。
为了达到上述设计,我们进行程序设计时,则需要满足以下的条件:(1)装饰器需要拥有与底层对象相同接口,这是为了客户对象可以像和底层对象交互的方式那样,同装饰器进行交互。(2)装饰器对象需要包含实际对象的引用,因为装饰器对象接受来自客户对象的所有请求(调用)。然后,将这些调用转发给底层对象,此时,就需要一个东西指引装饰器找到目标对象。
(1)首先,我们举一个书上的例子:有一个日志打印程序,打印的方式有两种,第一种直接打印到控制台,第二种直接打印到文件。于是有了如下的UML图:
现在,我们想拓展两个功能,一个是对日志文件加密功能,一个是将日志转为HTML然后再打印。在不改变原来的代码的基础上,进行功能拓展,通过继承无疑是较好的方式,于是有了如下的类图:
从图中,我们可以直观的看出,直接通过继承来扩展功能的方法的灵活性是比较差的,在某些场合下,甚至会导致大量子类的产生,维护性和可读性都将大大下降。于是,装饰器模式提出了如下的解决办法:
LoggerDecorator类实现Logger接口,这样保证了拓展的功能和原来的功能,依然具有共同的接口,便于类型的转换。拓展的功能HTMLLogger类和EncryptLogger类继承LoggerDecorator类,重写log方法,从而实现拓展的功能。
注意:LoggerDe corator类持有接口的引用logger,这样,在客户端不管实例化ConsoleLogger打印到控制台类还是实例化打印到文件的FileLogger类,都可以向上转型为接口。
代码:
package top.fang.decorator;
/**
* 公共的打印接口
*/
public interface Log {
void log(String msg);
}
package top.fang.decorator;
/**
* 实现打印机口的两个具体打印方法
* 打印到文件
* 打印到控制台
*/
public class FileLogImpl implements Log {
@Override
public void log(String msg) {
System.out.println("===打印到文件当中===");
System.out.println(msg);
}
}
package top.fang.decorator;
public class ConsoleLogImpl implements Log {
@Override
public void log(String msg) {
System.out.println("====打印到控制台====");
System.out.println(msg);
}
}
package top.fang.decorator;
public abstract class DecoratorLogImpl implements Log {
protected Log log;
public DecoratorLogImpl(Log log){
super();
this.log = log;
}
}
package top.fang.decorator;
/**
* 拓展功能的两个类,持有Log接口的引用,根据客户端实例化的具体对象,自动调用相应的类
* 转为HTML
* 消息加密
*/
public class HTMLFileExtendsDecoratorLogImpl extends DecoratorLogImpl {
public HTMLFileExtendsDecoratorLogImpl(Log log){
super(log);
}
@Override
/**
* log 为抽象父类的属性
*/
public void log(String msg) {
String htmlMsg = "传入的消息已转为HTML";
log.log(htmlMsg);
}
}
package top.fang.decorator;
public class EncryLogExtendsDecoratorLogImpl extends DecoratorLogImpl {
public EncryLogExtendsDecoratorLogImpl(Log log){
super(log);
}
@Override
public void log(String msg) {
String encryMsg = "传入的消息已经加密";
log.log(encryMsg);
}
}
测试
package top.fang.decorator;
public class Cilent {
public static void main(String[] args) {
/**
* 这里我们还可以用工厂的方法在造实例对象
*/
HTMLFileExtendsDecoratorLogImpl test =
new HTMLFileExtendsDecoratorLogImpl(new FileLogImpl());
test.log("天上掉馅饼了");
}
}
适配器
类适配器
适配器是用于接口转换的,但注意此接口不是Java中定义的接口那样,它是一个类里对外提供外面对象访问的方法。加入有一个interface(为了区别开来,实际意义上的接口用英文表示)它提供的方法名是isValid(),并且在这个方法下也有对个实现者了,现在在拓展业务的过程中,有个类单独命名了这样的一个方法isChinaVaild(),那么问题来了,在客户端方面,我们都是依赖interface来访问具体业务里的isValid方法的,但现在我们做不到依赖这个interface访问到isChinaValid方法了:
public interface Test{
void isValid();
}
public USA implements Test{
void isValid{
//实现
}
}
//客户端
Test test =new USA()//或者通过工厂获得对象
test.isValid() //只依赖接口就能访问USA的isValid方法
//现在拓展了中国业务,由于具体需求,我们的方法名变了,还有返回值,此时不能通过上面的方式访问了
public China implements Test{
String isChinaValid(){
//实现
}
}
根据上面例子,我们的适配器模式提出了一种解决办法,增加一个类继承China类并实现Test接口,然后在isValid方法中完成对isChianV alid方法的调用,即将拥有独立业务的China类适配到Test接口。
public class ChinaAdapter extends China implements Test{
void isValid(){
isChinaValid();
}
}
就这样添加一个类,就完成了适配,这种模式非常常见,如果了解Java的组件的话,肯定不陌生,它在组件中就定义了很多适配器,譬如鼠标点击,键盘输入都有相应适配器来完成具体的事件类到监听类接口的转换。
对象适配器
上面说的例子中,适配器除了继承适配源(China)之外,还要实现接口Test,但如果Test不再是一个接口而是一个抽象类,那么由于Java只支持单继承(除了接口),则不能采用上面的模式进行设计了,于是便有了对象适配器。
此时,我们只能选择继承抽象类,然后在子类中持有适配源对象(China)
abstract class Test{
void isValid();
}
class ChinaAdapter extends Test{
private Chian china;
public Test(China china){
this.china = china;
}
void isValid{
china.isChinaValid();
}
}
对比这两种方式,后者显得更加灵活,但也有缺点,因为构造适配器对象时,需要传入一个适配源对象(China),这样客户端就知道它存在的事实了。
责任链
责任链模式在如今的设计中也是常用的,并有相应的框架进行支持,譬如JBPM,工作流引擎。它的设计思想如下:假如有一个请求,它的潜在处理对象有A,B,C,D,此时,我们可以对A、B、C、D以一种顺序进行排序,然后请求按照这个顺序逐层走。对于接受到请求的A,B,C,D,它们只负责做这样的决定:进行处理或者传递给下一个对象,不负责下一个能不能做。
典型的应用就是一个公司,不同级别的人,对某个项目批准的金额是有上限的,十万可能是经理级别,100万需要董事长批准了。
package responsibility;
public abstract class Requesthandler {
private Requesthandler nextHandler;
public Requesthandler getNextHandler() {
return nextHandler;
}
//设置下一个传递对象
public void setNextHandler(Requesthandler nextHandler) {
this.nextHandler = nextHandler;
}
//关键方法,不同的领导具体实现该方法
public abstract boolean handle(Requester requester);
}
class Boss extends Requesthandler{
private static int LIMIT = 1000000;
@Override
public boolean handle(Requester requester) {
if(requester.getMoney()<LIMIT){
System.out.println("boss可以处理");
return true;
}else{
return getNextHandler().handle(requester);
}
}
}
class Manager extends Requesthandler{
private static int LIMIT = 100000;
@Override
public boolean handle(Requester requester) {
if(requester.getMoney()<LIMIT){
System.out.println("经理可以处理");
return true;
}
System.out.println("经理不可以处理传至上一级");
return getNextHandler().handle(requester);
}
}
public class Requester{
private String id;
private int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
class Client{
private Boss boss;
private Manager manager;
public static void main(String[] args) {
Client client = new Client();
client.createWorkFlow();//创造一个工作流
Requester requester = new Requester();
requester.setId("项目001");
requester.setMoney(200000);//20万
client.manager.handle(requester);
}
private void createWorkFlow() {
boss = new Boss();//boss为最上级
manager = new Manager();
manager.setNextHandler(boss);//设置经理的上一级为boss
}
}
// console
经理不可以处理传至上一级
boss可以处理
上面的例子中,我们定义了一个handle类,它定义下一级要传递的对象的方法,还有一个抽象方法,表示各个级别的领导相应的权利,各个领导类继承该类,并具体化该方法,最后在客户端中,我们创建一个工作流,最低级的经理设置上级为boss,boss做最后的处理。
通过以上模式,我们避免了将请求同时绑定在经理和boss上,从而降低请求和一系列潜在的处理对象之间的耦合。