在软件构件过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。
在这种情况下,我们可使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。
优点: 减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,同时也降低了类间的耦合。 缺点: 中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化。
随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理。
Facade模式是解耦系统外到系统内(单向)的对象关联关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。
通用抽象中介者:
public abstract class Mediator{
//定义同事类
protected ConcreteColleague1 c1;
protected ConcreteColleague2 c2;
//通过getter/setter方法把同事类注入进来
public ConcreteColleague1 getC1(){
return c1;
}
public void setC1(ConcreteColleague1 c1){
this.c1 = c1;
}
public ConcreteColleague2 getC2(){
return c2;
}
public void setC2(ConcreteColleague1 c2){
this.c2 = c2;
}
//中介者模式的业务逻辑
public abstract void doSomething1();
public abstract void doSomething2();
]
通用中介者:
public class ConcreteMediator extends Mediator{
public void doSomething1(){
//调用同事类的方法,只要是public方法都可以调用
super.c1.selfMethod1();
super.c2.selfMethod2();
}
public void doSomething2(){
//调用同事类的方法,只要是public方法都可以调用
super.c1.selfMethod1();
super.c2.selfMethod2();
}
}
抽象同事类:
public abstract class Colleague{
protected Mediator mediator;
public Colleague(Mediator mediator){
this.mediator = mediator;
}
}
具体同事类:
public class ConcreteColleague1 extends Colleague{
//通过构造函数传递中介者
public ConcreteColleague1(Mediator mediator){
super(mediator);
}
//自有方法 self-method
public void selfMethod1(){
//处理自己的业务逻辑
}
//依赖方法 dep-method
public void depMethod1(){
//处理自己的业务逻辑
//自己不能处理的业务逻辑,委托给中介者处理
super.mediator.doSomething1();
}
}
public class ConcreteColleague2 extends Colleague{
//通过构造函数传递中介者
public ConcreteColleague2(Mediator mediator){
super(mediator);
}
//自有方法 self-method
public void selfMethod2(){
//处理自己的业务逻辑
}
//依赖方法 dep-method
public void depMethod2(){
//处理自己的业务逻辑
//自己不能处理的业务逻辑,委托给中介者处理
super.mediator.doSomething2();
}
}
##动机(Motivation) 在软件构件过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。
在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构件一个解释器来解释这样的句子,从而达到解决问题的目的。
给定一个语言,定义它的文法的一中表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。
Interpreter模式的应用场合是Interpreter模式应用中的难点,只有满足“业务规则频繁变化,且类似的模式不断重复出现,并且容易抽象为语法规则的问题”才适合使用Interpreter模式。
使用Interpreter模式来表示文法规则,从而可以使用面向对象技巧来方便地“扩展”文法。
抽象表达式:
public void class Expression{
//每个表达式必须有一个解析任务
public abstract Object interpreter(Context ctx);
}
终结符表达式:
public class TerminalExpression extends Expression{
//通常终结符表达式只有一个,但是有多个对象
public Object interpreter(Context ctx){
return null;
}
}
非终结符表达式:
public class NonterminalExpression extends Expression{
//每个非终结符表达式都会对其他表达式产生依赖
public NonterminalExpression(Expression... expression){ }
public Object interpreter(Context ctx){
//进行文法处理
return null;
}
}
客户类:
public class Client{
public static void main(String[] args){
Context ctx = new Context();
//通常定一个语法容器,容纳一个具体的表达式,通常为ListArray、LinkedList、Stack等类型
Stack<Expression> stack = null;
for( ; ; ){
//进行语法判断,并产生递归调用
}
//产生一个完整的语法树,由各个具体的语法分析进行解析
Expression exp = stack.pop();
//具体元素进入场景
exp.interpreter(ctx);
}
}
耦合是软件不能抵御变化灾难的根本性原因。不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系。
在软件构件过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销、重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,可以提提供命令的撤销和恢复功能。
Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。
实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。
通过使用Composite模式,可以将多个“命令”封装为一个“复合命令”MacroCommand。
通用Receiver类:
public abstract class Receiver{
public abstract void doSomething();
}
具体的Receiver类:
public class ConcreteReceiver extends Receiver{
public void doSomething() {}
}
抽象的Command类:
public abstract class Command{
//定义一个子类的全局共享变量
protected final Receiver receiver;
//实现类必须定义一个接受者
public Command(Receiver receiver){
this.receiver = receiver;
}
//每个命令类都必须有一个执行命令的方法
public abstract void execute();
}
具体的Command类:
public class ConcreteCommand extends Command{
//声明自己的默认接受者
public ConcreteCommand(){
super(new ConcreteReciver());
}
//设置新的接受者
public ConcreteCommand(Receiver receiver){
super(receiver);
}
//每个命令类都必须有一个执行命令的方法
public abstract void execute(){
super.receiver.doSomething();
}
}
调用者Invoker类:
public clas Invoker{
private Command command;
//接受命令
public void setCommand(Command command){
this.command = command;
}
//执行命令
public void action(){
this.command.execute();
}
}
在软件构件过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却又很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
基本方法:也叫基本操作,是由子类实现的方法,并且在模板方法中被调用。 模板方法:可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(抽象方法的多态)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用。
再具体实现方面,被Template Method调用的方法可以具有实现,也可以没有任何实现(抽象方法),但一般推荐将他们设置为protected方法。
抽象模板类:
public abstract class AbstractClass{
//基本方法,用protected修饰
protected abstract void doSomething();
protected abstract void doAnything();
// 模板方法,用public修饰
public void templateMethod(){
this.doAnything();
this.doSomething();
}
}
具体模板类:
public class ConcreteClass extends AbstractClass{
protected abstract void doSomething(){}
protected abstract void doAnything(){}
}
场景类:
public class Client{
public static void main(Stirng[] args){
ConcreteClass clazz = new ConcreteClass();
clazz.templateMethod();
}
}
人们对于复杂的软件系统常常有一种处理手法,即增加一层间接层,从而对系统获得一种更为灵活、满足特定需求的解决方案。
在面向对象系统中,有些对象由于某种原因(比如对想创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等,直接访问会给使用者、或者系统结构带来很多麻烦。
如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。
普通代理:它的要求是客户端只能访问代理角色,不能访问真实角色
强制代理:必须通过真实角色查找到代理角色,也就是说由真实角色管理代理角色。
“增加一层间接层”是软件系统中许多复杂问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段。
具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy。
proxy并不一定要求保持接口的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。
静态代理类似于装饰者模式。
面向横切面编程,也就是AOP,其核心就是采用了动态代理机制。
结构:
动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。
抽象主题:
public interface Subject{
public void doSomething(String str);
}
真实主题:
public class RealSubject implements Subject{
public void doSomething(String str){
System.out.println("do something!"+str);
}
}
动态代理的Handler类:
public class MyInvocationHandler implements InvocationHandler{
//被代理的对象
private Object target = null;
//通过构造函数传递一个对象
public MyInvocationHandler(Object obj){
this.target = obj;
}
/代理方法
public Object invoke(Object proxy ,Method method,Object[] objs) thows Throwable{
//执行被代理的方法
return method.invoke(this.target,objs);
}
}
动态代理类:
public class DynamicProxy<T>{
public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHanler h){
//寻找JoinPoint连接点,AOP框架使用元数据定义
if(true){
//执行一个前置通知
(new BeforeAdvice()).exec();
}
}
//执行目标,并返回结果
return (T)Proxy.newProxyInstance(loader,interfaces,h);
}
具体业务的动态代理:
public class SubjectDynamicProxy extends DynamicProxy{
public static <T> newProxyInstance(Subject subject){
//获得ClassLoader
ClassLoader loader = subject.getClass().getClassLoader();
//获得接口数组
Class<?>[] classes = subject.getClass().getInterfaces(;
//获得handler
InvocationHandler handler = new MyInvocationHandler(subject);
return newProxyInstance(loader,classes,handler);
}
}
场景类:
public class Client{
public static void main(Stirng[] args){
//定义一个主题
Subject subject = new RealSubject();
//定义主题的代理
Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);
//代理的行为
proxy.doSomething("Finish");
}
}
面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。但是,在某些特殊的应用下,由于对象的数量太大,采用面向对象会给系统带来难以承受的内存开销。比如图形应用中的图元等对象、字处理应用中的字符对象等。
采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统找那个,从而带来很高的运行时代价——主要指内存需求方面的代价。
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?
运用共享对象有效地支持大量细粒度的对象。
上面享元模式的定义为我们提出了两个要求:细粒度和共享对象。我们知道分配太多的对象到应用程序中将有损程序的性能,同时还容易造成内存溢出,要避免这种情况,用到的就是共享技术,这里就需要提到内部状态和外部状态了。
因为要求细粒度对象,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
内部状态指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
我们举一个最简单的例子,棋牌类游戏大家都有玩过吧,比如说说围棋和跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色略多一点,但也是不太变化的,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,我们落子嘛,落子颜色是定的,但位置是变化的,所以方位坐标就是棋子的外部状态。
那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。
面向对象很好地解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。
Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。再具体实现方面,要注意对象状态的处理。
对象的数量太大从而导致对象内存开销加大——什么样的数量才算大?这需要我们仔细的根据具体应用情况进行评估,而不能凭空臆断。
-共享内存也有指针的开销,占四个字节,当对象所占内存小于四个字节就没有必要使用共享。
抽象享元角色:
public abstract class Flyweight{
//内部状态
private String intrinsic;
//外部状态
protected final String Extrinsic;
//要求享元角色必须接受外部状态
public Flyweight(String Extrinsic){
this.Extrinsic = Extrinsic;
}
//定义业务操作
public abstract void operate();
//内部状态的getter/setter
public String getIntrinsic(){
return intrinsic;
}
public void setIntrinsic(String intrinsic){
this.intrinsic = intrinsic;
}
}
具体享元角色:
public class ConcreteFlyweight extends Flyweight{
//接受外部状态
public ConcreteFlyweight(String Extrinsic){
super(Extrinsic);
}
//根据外部状态进行逻辑处理
public void operate() {}
}
享元工厂:
public class FlyweightFactory{
//定义一个池容器
private static HashMap<String,Flyweight> pool = new HashMap<String,Flyweight>();
//享元工厂
public static Flyweight getFlyweight(String Extrinsic){
//要返回的对象
Flyweight flyweight = null;
//在池中没有该对象
if(pool.containsKet(Extrinsic)){
flyweight = pool.get(Extrinsic);
}else{
//根据外部状态创建享元对象
flyweigh = new ConcreteFlyweight(Extrinsic);
//放置到池中
pool.put(Extrinsic,flyweight);
}
return flyweight;
}
}