Spring框架中的设计模式(一)

设计模式帮助开发人员遵循编程最佳实践。作为最流行的Web框架,Spring也使用了设计模式。

这篇文章会讲解Spring框架使用了哪些设计模式以及怎么使用的。该主题文章分成5部分,这是第一部分。这一部分中我们会覆盖Spring框架使用的四种设计模式 : 解释器interpreted,生成器builder,工厂方法factory method 和 抽象工厂abstract factory。每部分会首先解释一下模式的基本原理,然后给出一个Spring中的使用例子。

Spring设计模式 – 解释器 interpreter

现实世界中我们人类需要解释手势。根据文化不同,手势会有不同意思。我们的解释赋予了它们特定意义。编程中我们也需要解释一件事情然后决定它表示什么。这时候就会用到 解释器模式 interpreted design pattern

该模式建立在表达式(expression)和评估器(evaluator)之上。前者表达式(expression)表示一个需要分析的东西。而后者评估器(evaluator)知道组成表达式(expression)的字符的意义然后做分析。这个理解的过程在一个上下文(context)中进行。

Spring主要在 Spring Expression Language(SpEL) 中使用解释器模式。

这里很快地提醒一下,SpEL是一种表达式语言,由Spring中org.springframework.expression.ExpressionParser的实现来分析和执行表达式。该实现将字符串格式的SpEL表达式作为输入翻译得到org.springframework.expression.Expression实例。而上下文实例通过org.springframework.expression.EvaluationContext的实现来表示,比如: StandardEvaluationContext

一个SpEL例子,看起来可能是这样的:

Writer writer = new Writer();
writer.setName("Writer's name");
StandardEvaluationContext modifierContext = 
	new StandardEvaluationContext(subscriberContext);
modifierContext.setVariable("name", "Overriden writer's name");
parser.parseExpression("name = #name").getValue(modifierContext);
System.out.println("writer's name is : " + writer.getName());
// 这里控制台上的输出会是 Overriden writer's name

输出会打印"Overriden writer’s name"。像你看到的那样,对象的属性通过一个ExpressionParser可以理解的表达式name = #name基于所提供的上下文(上面例子中的modifierContext)给修改了。

Spring设计模式 – 生成器 builder

生成器模式Builder design pattern是对象创建模式中的第一种模式。该模式用于简化构造复杂对象。为了理解这一概念,想象一个描述程序员简历的对象。这个对象中我们想保存个人信息(姓名地址等)还有技术能力信息(所掌握的语言,实现的项目等等)。该对象的构造看起来可能是这个样子:

// 方式1:使用构造函数
Programmer programmer = new Programmer(
							"first name", "last name", 
							"address Street 39", "ZIP code", 
							"City", "Country", birthDateObject, 
							new String[] {"Java", "PHP", "Perl", "SQL"}, 
							new String[] {"CRM system", "CMS system for government"});
// 或者 方式2 : 使用 setXXX 方法
Programmer programmer = new Programmer();
programmer.setName("first name");
programmer.setLastName("last name");
// ... 很多行setXXX 方法调用之后,添加项目信息
programmer.setProjects(new String[] {"CRM system", "CMS system for government"});

而生成器允许我们使用将值传给父类的内部生成器对象清楚地分解对象构建过程。如此这般:

public class BuilderTest {
 
  @Test
  public void test() {
    Programmer programmer = new Programmer.ProgrammerBuilder()
		    .setFirstName("F").setLastName("L")
            .setCity("City").setZipCode("0000A").setAddress("Street 39")
            .setLanguages(new String[] {"bash", "Perl"})
            .setProjects(new String[] {"Linux kernel"})
            .build();
    assertTrue("Programmer should be 'F L' but was '"+ programmer+"'", 
			    programmer.toString().equals("F L"));
  }
 
}
 
class Programmer {
  private String firstName;
  private String lastName;
  private String address;
  private String zipCode;
  private String city;
  private String[] languages;
  private String[] projects;
   
  private Programmer(String fName, String lName, 
					  String addr, String zip, String city, 
					  String[] langs, String[] projects) {
    this.firstName = fName;
    this.lastName = lName;
    this.address = addr;
    this.zipCode = zip;
    this.city = city;
    this.languages = langs;
    this.projects = projects;
  }
   
  public static class ProgrammerBuilder {
    private String firstName;
    private String lastName;
    private String address;
    private String zipCode;
    private String city;
    private String[] languages;
    private String[] projects;
     
    public ProgrammerBuilder setFirstName(String firstName) {
      this.firstName = firstName;
      return this;
    }
     
    public ProgrammerBuilder setLastName(String lastName) {
      this.lastName = lastName;
      return this;
    }
     
    public ProgrammerBuilder setAddress(String address) {
      this.address = address;
      return this;
    }
     
    public ProgrammerBuilder setZipCode(String zipCode) {
      this.zipCode = zipCode;
      return this;
    }
     
    public ProgrammerBuilder setCity(String city) {
      this.city = city;
      return this;
    }
     
    public ProgrammerBuilder setLanguages(String[] languages) {
      this.languages = languages;
      return this;
    }
    public ProgrammerBuilder setProjects(String[] projects) {
      this.projects = projects;
      return this;
    }
     
    public Programmer build() {
      return new Programmer(firstName, lastName, address, zipCode, city, 
					      languages, projects);
    } 
  }
   
  @Override
  public String toString() {
    return this.firstName + " "+this.lastName;
  }
   
}

这里你可以看到,复杂的对象构建过程隐藏在了一个生成器的后面,这个生成器是一个接收链式方法调用的内部静态类实例。Spring中,我们可以把该逻辑抽离到org.springframework.beans.factory.support.BeanDefinitionBuilder类。该类可以让我们程序化地定义一个bean。如我们在这篇关于bean factory post processors的文章中所看到的,BeanDefinitionBuilder中包含了好多向所关联的beanDefinition(实现了AbstractBeanDefinition抽象类)属性对象设置作用域,工厂方法,属性等等的方法。我们看一下这些方法的实现,理解一下它是怎么工作的:

public class BeanDefinitionBuilder {
       /**
    * The {@code BeanDefinition} instance we are creating.
    */
  private AbstractBeanDefinition beanDefinition;
 
  // ... some not important methods for this article
 
  // Some of building methods
  /**
    * Set the name of the parent definition of this bean definition.
    */
  public BeanDefinitionBuilder setParentName(String parentName) {
    this.beanDefinition.setParentName(parentName);
    return this;
  }
 
  /**
    * Set the name of the factory method to use for this definition.
    */
  public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) {
    this.beanDefinition.setFactoryMethodName(factoryMethod);
    return this;
  }
 
  /**
    * Add an indexed constructor arg value. The current index is tracked internally
    * and all additions are at the present point.
    * @deprecated since Spring 2.5, in favor of {@link #addConstructorArgValue}
    */
  @Deprecated
  public BeanDefinitionBuilder addConstructorArg(Object value) {
    return addConstructorArgValue(value);
  }
 
  /**
    * Add an indexed constructor arg value. The current index is tracked internally
    * and all additions are at the present point.
    */
  public BeanDefinitionBuilder addConstructorArgValue(Object value) {
    this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(
                    this.constructorArgIndex++, value);
    return this;
  }
 
  /**
    * Add a reference to a named bean as a constructor arg.
    * @see #addConstructorArgValue(Object)
    */
  public BeanDefinitionBuilder addConstructorArgReference(String beanName) {
    this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(
                    this.constructorArgIndex++, new RuntimeBeanReference(beanName));
    return this;
  }
 
  /**
    * Add the supplied property value under the given name.
    */
  public BeanDefinitionBuilder addPropertyValue(String name, Object value) {
    this.beanDefinition.getPropertyValues().add(name, value);
    return this;
  }
 
  /**
    * Add a reference to the specified bean name under the property specified.
    * @param name the name of the property to add the reference to
    * @param beanName the name of the bean being referenced
    */
  public BeanDefinitionBuilder addPropertyReference(String name, String beanName) {
    this.beanDefinition.getPropertyValues().add(name, new RuntimeBeanReference(beanName));
    return this;
  }
 
  /**
    * Set the init method for this definition.
    */
  public BeanDefinitionBuilder setInitMethodName(String methodName) {
    this.beanDefinition.setInitMethodName(methodName);
    return this;
  }
 
  // Methods that can be used to construct BeanDefinition
  /**
    * Return the current BeanDefinition object in its raw (unvalidated) form.
    * @see #getBeanDefinition()
    */
  public AbstractBeanDefinition getRawBeanDefinition() {
    return this.beanDefinition;
  }
 
  /**
    * Validate and return the created BeanDefinition object.
    */
  public AbstractBeanDefinition getBeanDefinition() {
    this.beanDefinition.validate();
    return this.beanDefinition;
  }
}

Spring设计模式 – 工厂方法 factory method

对象创建模式家族中第二个成员是工厂方法设计模式 factory method design pattern。它完全适合跟Spring框架这样的动态环境一起工作。事实上,该模式允许通过一个公开静态方法进行的对象初始化,该方法叫做工厂方法。在这个概念里,我们需要定义一个创建对象的接口。但是对象的创建是由使用目标对象的类来操作的。

在跳到Spring世界之前,我们看一个Java世界的例子:

// 该例子没有使用Spring框架的功能,而是基于Java SE
public class FactoryMethodTest {
 
  @Test
  public void test() {
    Meal fruit = Meal.valueOf("banana");
    Meal vegetable = Meal.valueOf("carrot");
    assertTrue("Banana should be a fruit but is "+fruit.getType(), 
		    fruit.getType().equals("fruit"));
    assertTrue("Carrot should be a vegetable but is "+vegetable.getType(), 
		    vegetable.getType().equals("vegetable"));
  }
 
}
 
class Meal {
         
  private String type;
 
  public Meal(String type) {
    this.type = type;
  }
 
  public String getType() {
    return this.type;
  }
 
  // 工厂方法例子 -- 根据当前上下文创建不同的对象
  public static Meal valueOf(String ingredient) {
    if (ingredient.equals("banana")) {
      return new Meal("fruit");
    }
    return new Meal("vegetable");
  }
}

Spring中我们可以使用指定的工厂方法创建bean。这个方法就跟上面例子中的valueOf的功能一样。它是静态的,可以不带参数也可以带有多个参数。为了更好地理解这一点,我们看一个真实的例子。首先是配置文件:

<bean id="welcomerBean" class="com.mysite.Welcomer" factory-method="createWelcomer">
    <constructor-arg ref="messagesLocator">
	constructor-arg>
bean>
 
<bean id="messagesLocator" class="com.mysite.MessageLocator">
    <property name="messages" value="messages_file.properties">
	property>
bean>

然后是这个初始化过程所关注的bean类:

public class Welcomer {
  private String message;
   
  public Welcomer(String message) {
    this.message = message;
  }
 
  public static Welcomer createWelcomer(MessageLocator messagesLocator) {
    Calendar cal = Calendar.getInstance();
    String msgKey = "welcome.pm";
    if (cal.get(Calendar.AM_PM) == Calendar.AM) {
      msgKey = "welcome.am";
    }
    return new Welcomer(messagesLocator.getMessageByKey(msgKey));
  }
}

当Spring将要构建bean实例welcomerBean,它不会直接通过经典的构造函数模式,而是通过定义一个静态工厂方法createWelcomer的模式。这里也需要注意一点是该方法接收了一些参数(MessageLocator bean实例,持有所有可用的消息)。

Spring设计模式 – 抽象工厂 abstract factory

最后一个模式,抽象工厂设计模式 abstract factory design pattern,看起来跟工厂方法很像。区别是我们可以把抽象工厂想象成现实世界中的某个工厂,他们会提供一些需要的物品。工厂可能有这些组成部分:抽象工厂,抽象产品,产品和客户。更明确地讲,抽象工厂定义了构建对象的方式。抽象产品是这个构建过程的结果。产品是同一构建过程的具体产出物。客户是向抽象工厂要求创建商品的某个人。

还是老样子,在我们进入Spring的细节之前,我们先通过简单Java代码描述一下这个概念 :

public class FactoryTest {
 
  // 测试方法,可以理解成用于表示客户行为
  @Test
  public void test() {
    Kitchen factory = new KitchenFactory();
    KitchenMeal meal = factory.getMeal("P.1");
    KitchenMeal dessert = factory.getDessert("I.1");
    assertTrue("Meal's name should be 'protein meal' and was '"+meal.getName()+"'", 
			    meal.getName().equals("protein meal"));
    assertTrue("Dessert's name should be 'ice-cream' and was '"+dessert.getName()+"'", 
			    dessert.getName().equals("ice-cream"));
  }
 
}
 
// 抽象工厂
abstract class Kitchen {
  public abstract KitchenMeal getMeal(String preferency);
  public abstract KitchenMeal getDessert(String preferency);
}
 
// 抽象工厂的具体实现
class KitchenFactory extends Kitchen {
  @Override
  public KitchenMeal getMeal(String preferency) {
    if (preferency.equals("F.1")) {
      return new FastFoodMeal();
    } else if (preferency.equals("P.1")) {
      return new ProteinMeal();
    }
    return new VegetarianMeal();
  }
 
  @Override
  public KitchenMeal getDessert(String preferency) {
    if (preferency.equals("I.1")) {
      return new IceCreamMeal();
    }
    return null;
  }
}
 
// 抽象产品
abstract class KitchenMeal {
  public abstract String getName();
}
 
// 具体产品
class ProteinMeal extends KitchenMeal {
  @Override
  public String getName() {
    return "protein meal";
  }
}
 
class VegetarianMeal extends KitchenMeal {
  @Override
  public String getName() {
    return "vegetarian meal";
  }
}
 
class FastFoodMeal extends KitchenMeal {
  @Override
  public String getName() {
    return "fast-food meal";
  }
}
 
class IceCreamMeal extends KitchenMeal {
  @Override
  public String getName() {
    return "ice-cream";
  }
}

从该例子我们可以看到,抽象工厂封装了对象的创建。这里对象的创建也可以不使用典型的构造方法模式而是采用工厂方法模式。Spring中,抽象工厂的例子是org.springframework.beans.factory.BeanFactory。利用该接口的实现,我们可以访问Spring容器内的bean。根据所采用的策略,getBean方法可能会返回已经创建的对象(共享实例,单例作用域)或者新建一个对象(prototype类型)。BeanFactory有各种实现: ClassPathXmlApplicationContext, XmlWebApplicationContext, StaticWebApplicationContext, StaticPortletApplicationContext, GenericApplicationContext, StaticApplicationContext。下面你可以看到一个Spring Web应用中使用抽象工厂的例子 :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:test-context.xml"})
public class TestProduct {
         
  @Autowired
  private BeanFactory factory;
   
  @Test
  public void test() {
    System.out.println("Concrete factory is: "+factory.getClass());
    assertTrue("Factory can't be null", factory != null);
    ShoppingCart cart = (ShoppingCart) factory.getBean("shoppingCart");
    assertTrue("Shopping cart object can't be null", cart != null);
    System.out.println("Found shopping cart bean:"+cart.getClass());
  }
}

这个例子中,抽象工厂由BeanFactory接口表示。实现类会通过System.out打印输出到控制台,其对应类为 : org.springframework.beans.factory.support.DefaultListableBeanFactory。抽象产品是Object。具体产品,这个例子里面,是将抽象产品Object对象强制类型转成的一个ShoppingCart实例。

该文章介绍了使用设计模式正确地组织程序代码的特别有趣的世界。这里,我们看到了解释器interpreter,生成器builder,工厂方法factory method和工厂factory(指的就是abstract factory)模式在Spring框架中的应用。第一个模式解释器interpreter帮助解释SpEL方式表达的文本。其他三个模式都属于对象创建模式,Sping中它们三个的主要目的都是帮助简化对象创建。他们通过分解复杂对象的构建过程或者将初始化过程集中到某些通用点来达到该目的。

英文原文

该系列文章目录

Spring框架中的设计模式(五)
Spring框架中的设计模式(四)
Spring框架中的设计模式(三)
Spring框架中的设计模式(二)
Spring框架中的设计模式(一)

你可能感兴趣的:(spring,Spring,Web)