Spring框架中使用的4種設計模式:解釋器,構(gòu)建器,工廠方法和抽象工廠。
解釋器設計模式
此模式基于表達式和評估器部分件已。第一個代表一個要分析的事情。這個分析是由評價者來做出的元暴,它們知道構(gòu)成表達的人物的意義篷扩。不必要的操作是在一個上下文中進行的。
Spring主要以Spring Expression Language(Spel)為例茉盏。這里快速提個醒鉴未,SpEL是一種由Spring的org.springframework.expression.ExpressionParser實現(xiàn)分析和執(zhí)行的語言枢冤。這些實現(xiàn)使用作為字符串給出的Spel表達式,并將它們轉(zhuǎn)換為org.springframework.expression.Expression的實例铜秆。上下文組件由org.springframework.expression.EvaluationContext實現(xiàn)表示淹真,例如: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”连茧。如你所見核蘸,一個對象的屬性是通過一個表達式name = #name
進行修改的,這個表達式只有在ExpressionParser
才能理解啸驯,因為提供了context
(前面的樣例中的modifierContext
實例)客扎。
建設者模式
建設者設計模式是屬于創(chuàng)建對象模式三劍客的第一種模式。該模式用于簡化復雜對象的構(gòu)造罚斗。要理解這個概念徙鱼,想象一個說明程序員簡歷的對象。在這個對象中惰聂,我們想存儲個人信息(名字疆偿,地址等)以及技術信息(知識語言咱筛,已實現(xiàn)的項目等)搓幌。該對象的構(gòu)造可能如下所示:
// with constructor
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"});
// or with setters
Programmer programmer = new Programmer();
programmer.setName("first name");
programmer.setLastName("last name");
// ... multiple lines after
programmer.setProjects(new String[] {"CRM system", "CMS system for government"});
Builder允許我們通過使用將值傳遞給父類的內(nèi)部構(gòu)建器對象來清楚地分解對象構(gòu)造。所以對于我們這個程序員簡歷的對象的創(chuàng)建迅箩,構(gòu)建器可以看起來像:
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;
}
}
可以看出溉愁,構(gòu)建器后面隱藏了對象構(gòu)造的復雜性,內(nèi)部靜態(tài)類接受鏈接方法的調(diào)用饲趋。在Spring中拐揭,我們可以在org.springframework.beans.factory.support.BeanDefinitionBuilder類中檢索這個邏輯。這是一個允許我們以編程方式定義bean的類,BeanDefinitionBuilder包含幾個方法奕塑,它們?yōu)?strong>AbstractBeanDefinition抽象類的相關實現(xiàn)設置值堂污,比如作用域,工廠方法龄砰,屬性等盟猖。想看看它是如何工作的,請查看以下這些方法:
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;
}
}
工廠方法
創(chuàng)建對象模式三劍客的第二個成員是工廠方法設計模式换棚。它完全適于使用動態(tài)環(huán)境作為Spring框架式镐。實際上,這種模式允許通過公共靜態(tài)方法對象進行初始化固蚤,稱為工廠方法娘汞。在這個概念中,我們需要定義一個接口來創(chuàng)建對象夕玩。但是創(chuàng)建是由使用相關對象的類創(chuàng)建的你弦。
但是在跳到Spring世界之前惊豺,讓我們用Java代碼做一個例子:
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;
}
// Example of factory method - different object is created depending on current context
public static Meal valueOf(String ingredient) {
if (ingredient.equals("banana")) {
return new Meal("fruit");
}
return new Meal("vegetable");
}
}
在Spring中,我們可以通過指定的工廠方法創(chuàng)建bean禽作。該方法與以前代碼示例中看到的valueOf方法完全相同扮叨。它是靜態(tài)的,可以采取沒有或多個參數(shù)领迈。為了更好地了解案例彻磁,讓我們來看一下實例。首先搞定下配置:
<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>
現(xiàn)在請關注這個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將構(gòu)造welcomerBean時狸捅,它不會通過傳統(tǒng)的構(gòu)造函數(shù)衷蜓,而是通過定義的靜態(tài)工廠方法createWelcomer來實現(xiàn)。還要注意尘喝,這個方法接受一些參數(shù)(MessageLocator bean的實例包含所有可用的消息) 標簽磁浇,通常保留給傳統(tǒng)的構(gòu)造函數(shù)。
抽象工廠
抽象的工廠設計模式朽褪,看起來類似于工廠方法置吓。不同之處在于,我們可以將抽象工廠視為這個詞的工業(yè)意義上的工廠缔赠,即衍锚。作為提供所需對象的東西。工廠部件有:抽象工廠嗤堰,抽象產(chǎn)品戴质,產(chǎn)品和客戶。更準確地說踢匣,抽象工廠定義了構(gòu)建對象的方法告匠。抽象產(chǎn)品是這種結(jié)構(gòu)的結(jié)果。產(chǎn)品是具有同樣結(jié)構(gòu)的具體結(jié)果离唬『笞ǎ客戶是要求創(chuàng)造產(chǎn)品來抽象工廠的人。
同樣的输莺,在進入Spring的細節(jié)之前戚哎,我們將首先通過示例Java代碼說明這個概念:
public class FactoryTest {
// Test method which is the client
@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 factory
abstract class Kitchen {
public abstract KitchenMeal getMeal(String preferency);
public abstract KitchenMeal getDessert(String preferency);
}
// concrete factory
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 product
abstract class KitchenMeal {
public abstract String getName();
}
// concrete products
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";
}
}
我們可以在這個例子中看到,抽象工廠封裝了對象的創(chuàng)建模闲。對象創(chuàng)建可以使用與經(jīng)典構(gòu)造函數(shù)一樣使用的工廠方法模式建瘫。在Spring中,工廠的例子是org.springframework.beans.factory.BeanFactory尸折。通過它的實現(xiàn)啰脚,我們可以從Spring的容器訪問bean。根據(jù)采用的策略,getBean方法可以返回已創(chuàng)建的對象(共享實例橄浓,單例作用域)或初始化新的對象(原型作用域)粒梦。在BeanFactory
的實現(xiàn)中,我們可以區(qū)分:ClassPathXmlApplicationContext
荸实,XmlWebApplicationContext
匀们,StaticWebApplicationContext
,StaticPortletApplicationContext
准给,GenericApplicationContext
泄朴,StaticApplicationContext
。
@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的實例畔规。它的抽象產(chǎn)物是一個對象局扶。在我們的例子中,具體的產(chǎn)品就是被強轉(zhuǎn)為ShoppingCart實例的抽象產(chǎn)品(Object)叁扫。
介紹了通過設計模式來正確組織的我們實現(xiàn)良好的編程風格三妈。在這里,我們可以看到在Spring框架中使用解釋器莫绣,構(gòu)建器畴蒲,工廠方法和工廠。第一個是幫助解釋以SpEL表達的文本兔综。三個最后的模式屬于創(chuàng)建設計模式的三劍客饿凛,它們在Spring中的主要目的是簡化對象的創(chuàng)建狞玛。他們通過分解復雜對象(構(gòu)建器)的初始化或通過集中在公共點的初始化來做到對象的創(chuàng)建软驰。