通过之前四篇文章,我们看到Spring采用了大量对象创建和结构设计模式。这些大都不是行为设计模式,作为该系列最后一篇关于Spring设计模式的文章,本文再讲两个行为设计模式。
这篇文章我们会讲两个行为设计模式:命令模式和访问者模式。
Spring 设计模式 - 命令模式command
本文第一个我们要讲述的模式是命令模式。它允许将一个请求(request
)封装到一个对象然后将其跟一个回调动作(callback action)关联。请求(request
)封装到命令(command
)对象中,而请求的结果会被转发给接收者(receiver
)。命令并不自己执行而是由一个调用者(invoker
)执行。为了更好地理解这一思路的要点,想象一下管理一台服务器的一个场景。管理员(invoker
)在以命令行(command
)方式启动了某些操作,而这些命令行的结果被传输到了被管理的服务器上(receiver
)。这里要感谢的是终端,它在这里可以被认为是一个客户端。让我们把这个例子写成一个JUnit测试用例:
public class CommandTest {
// This test method is a client
@Test
public void test() {
Administrator admin = new Administrator();
Server server = new Server();
// start Apache
admin.setCommand(new StartApache(server));
admin.typeEnter();
// start Tomcat
admin.setCommand(new StartTomcat(server));
admin.typeEnter();
// check executed commands
int executed = server.getExecutedCommands().size();
assertTrue("Two commands should be executed but only "+
executed+ " were", executed == 2);
}
}
// commands
abstract class ServerCommand {
protected Server server;
public ServerCommand(Server server) {
this.server = server;
}
public abstract void execute();
}
class StartTomcat extends ServerCommand {
public StartTomcat(Server server) {
super(server);
}
@Override
public void execute() {
server.launchCommand("sudo service tomcat7 start");
}
}
class StartApache extends ServerCommand {
public StartApache(Server server) {
super(server);
}
@Override
public void execute() {
server.launchCommand("sudo service apache2 start");
}
}
// invoker
class Administrator {
private ServerCommand command;
public void setCommand(ServerCommand command) {
this.command = command;
}
public void typeEnter() {
this.command.execute();
}
}
// receiver
class Server {
// as in common terminals, we store executed commands in history
private List<String> executedCommands = new ArrayList<String>();
public void launchCommand(String command) {
System.out.println("Executing: "+command+" on server");
this.executedCommands.add(command);
}
public List<String> getExecutedCommands() {
return this.executedCommands;
}
}
这个测试用例应该能够通过并且输出两个命令:
Executing: sudo service apache2 start on server
Executing: sudo service tomcat7 start on server
命令模式不仅能够封装请求request
(ServerCommand)发送给接收者receiver
(Server),而且能够更好地处理请求。在上面的例子中,将命令执行做一个历史记录可以认为是对请求的一个更好的处理。Spring中,我们把命令设计模式抽取到了bean工厂的后置处理器(beans factory post processor)中。这些后置处理器会被应用上下文在bean创建时执行用来对bean做一些修改(如果你想了解关于后置处理器更多的内容,请看一下关于bean工厂后置处理器的文章)。
当我们从上面的命令模式逻辑切换到Spring的bean工厂后置处理器时,我们可以观察到如下角色扮演:后置处理器bean,也就是BeanFactoryPostProcessor
接口的实现类,扮演了命令command
,org.springframework.context.support.PostProcessorRegistrationDelegate
是调用者invoker
(他会执行所有注册的后置处理器bean的postProcessBeanFactory
方法),接收者receiver
是org.springframework.beans.factory.config.ConfigurableListableBeanFactory
,其元素,也就是各个bean会在它们被构造被修改(比如: bean的属性可以在bean实例初始化之前被修改)。
另外,在我们的JUnit例子中,我们也演示了"更好地处理"请求这一点,也就是做一个命令执行历史记录。类似地,PostProcessorRegistrationDelegate
里面包含一个内部类BeanPostProcessorChecker
,它会在一个bean不符合被处理的条件时日志输出该情况。
Spring 设计模式 - 访问者模式visitor
第二种模式我们来讲访问者模式visitor
。该模式背后的思想在于它使一个对象变成其他类型的对象可访问(visitable)。在这个简短的定义之后,你可能可以想到,使用此模式的对象会被堪称访问者(visitor
)或者时可访问对象(object visitable
),前者(访问者)访问后者(可访问对象)。,这个模式真实世界中的一个例子是机修工,他会检查汽车部件,比如轮胎,刹车和引擎,然后下结论说这车还能不能用。我们使用JUnit看一下这个例子:
public class VisitorTest {
@Test
public void test() {
CarComponent car = new Car();
Mechanic mechanic = new QualifiedMechanic();
car.accept(mechanic);
assertTrue("After qualified mechanics visit, the car should be broken",
car.isBroken());
Mechanic nonqualifiedMechanic = new NonQualifiedMechanic();
car.accept(nonqualifiedMechanic);
assertFalse("Car shouldn't be broken becase non qualified mechanic " +
" can't see breakdowns", car.isBroken());
}
}
// visitor
interface Mechanic {
public void visit(CarComponent element);
public String getName();
}
class QualifiedMechanic implements Mechanic {
@Override
public void visit(CarComponent element) {
element.setBroken(true);
}
@Override
public String getName() {
return "qualified";
}
}
class NonQualifiedMechanic implements Mechanic {
@Override
public void visit(CarComponent element) {
element.setBroken(true);
}
@Override
public String getName() {
return "unqualified";
}
}
// visitable
abstract class CarComponent {
protected boolean broken;
public abstract void accept(Mechanic mechanic);
public void setBroken(boolean broken) {
this.broken = broken;
}
public boolean isBroken() {
return this.broken;
}
}
class Car extends CarComponent {
private boolean broken = false;
private CarComponent[] components;
public Car() {
components = new CarComponent[] {
new Wheels(), new Engine(), new Brake()
};
}
@Override
public void accept(Mechanic mechanic) {
this.broken = false;
if (mechanic.getName().equals("qualified")) {
int i = 0;
while (i < components.length && this.broken == false) {
CarComponent component = components[i];
mechanic.visit(component);
this.broken = component.isBroken();
i++;
}
}
// if mechanic isn't qualified, we suppose that
// he isn't able to see breakdowns and so
// he considers the car as no broken
// (even if the car is broken)
}
@Override
public boolean isBroken() {
return this.broken;
}
}
class Wheels extends CarComponent {
@Override
public void accept(Mechanic mechanic) {
mechanic.visit(this);
}
}
class Engine extends CarComponent {
@Override
public void accept(Mechanic mechanic) {
mechanic.visit(this);
}
}
class Brake extends CarComponent {
@Override
public void accept(Mechanic mechanic) {
mechanic.visit(this);
}
}
这个例子里,我们看到有两个机修工Mechanic
(visitor):一个合格的机修工和一个不合格的机修工。他们看到的可访问对象是车Car
。通过车的accept
方法来决定对访问者visitor应用哪种策略。当访问者合格时,Car
允许他分析所有部件。而如果访问者不合格,Car
会认为访问者的介入无效,拒绝它分析各个部件,而最终通过isBroken()
方法返回一个false
。Spring在bean配置中使用了访问者设计模式。想看到这一点,我们可以看看用来分析bean元数据并将它们变成字符串(例子:XML属性中的作用域或者工厂方法名字)或者对象(例子:构造函数定义中参数)的org.springframework.beans.factory.config.BeanDefinitionVisitor
对象。而所处理后的值会被设置到所分析的bean对应的BeanDefinition
实例中去。想知道它怎么工作的,看下面BeanDefinitionVisitor
的的一个代码片段:
/**
* Traverse the given BeanDefinition object and the MutablePropertyValues
* and ConstructorArgumentValues contained in them.
* @param beanDefinition the BeanDefinition object to traverse
* @see #resolveStringValue(String)
*/
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
visitPropertyValues(beanDefinition.getPropertyValues());
ConstructorArgumentValues cas = beanDefinition.
getConstructorArgumentValues();
visitIndexedArgumentValues(cas.
getIndexedArgumentValues());
visitGenericArgumentValues(cas.
getGenericArgumentValues());
}
protected void visitParentName(BeanDefinition beanDefinition) {
String parentName = beanDefinition.getParentName();
if (parentName != null) {
String resolvedName = resolveStringValue(parentName);
if (!parentName.equals(resolvedName)) {
beanDefinition.setParentName(resolvedName);
}
}
}
这个例子中,只有访问方法,没有跟我们上面机修工例子里面类似的合格/不合格机修工的访问者控制机制。这里访问者的访问分析给定bean定义BeanDefinition
对象的参数并将其替换成解决之后的对象。
总结
这篇文章,该系列关于Spring设计模式的的最后一篇,我们讲了两种行为设计模式:命令设计模式,用于对bean工厂进行后置处理;和访问者模式,用于将bean定义中的参数转变成对象领域的参数(字符串或者对象)。
英文原文
该系列文章目录
Spring框架中的设计模式(五)Spring框架中的设计模式(四)Spring框架中的设计模式(三)Spring框架中的设计模式(二)Spring框架中的设计模式(一)