設計模式有助於遵循良好的編程實踐。作爲最流行的Web框架之一的Spring框架也使用其中的一些。
本文將介紹Spring Framework中使用的設計模式。這是5篇專題文章的第一部分。這次我們將發現Spring框架中使用的4種設計模式:解釋器,構建器,工廠方法和抽象工廠。每部分將首先解釋給定模式的原理。緊接着,將會使用Spring的一個例子來加深理解。
解釋器設計模式
在現實世界中,我們人類需要解釋手勢。他們可以對文化有不同的含義。這是我們的解釋,給他們一個意義。在編程中,我們還需要分析一件事情,並決定它是什麼意思。我們可以用解釋設計模式來做。
此模式基於表達式和評估器部分。第一個代表一個要分析的事情。這個分析是由評價者來做出的,它們知道構成表達的人物的意義。不必要的操作是在一個上下文中進行的。
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”。如你所見,一個對象的屬性是通過一個表達式name = #name進行修改的,這個表達式只有在ExpressionParser才能理解,因爲提供了context(前面的樣例中的modifierContext實例)。
建設者模式
建設者設計模式是屬於創建對象模式三劍客的第一種模式。該模式用於簡化複雜對象的構造。要理解這個概念,想象一個說明程序員簡歷的對象。在這個對象中,我們想存儲個人信息(名字,地址等)以及技術信息(知識語言,已實現的項目等)。該對象的構造可能如下所示:
-
// 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允許我們通過使用將值傳遞給父類的內部構建器對象來清楚地分解對象構造。所以對於我們這個程序員簡歷的對象的創建,構建器可以看起來像:
-
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工廠後處理器的文章中看到它,BeanDefinitionBuilder包含幾個方法,它們爲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框架。實際上,這種模式允許通過公共靜態方法對象進行初始化,稱爲工廠方法。在這個概念中,我們需要定義一個接口來創建對象。但是創建是由使用相關對象的類創建的。
但是在跳到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中,我們可以通過指定的工廠方法創建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將構造welcomerBean時,它不會通過傳統的構造函數,而是通過定義的靜態工廠方法createWelcomer來實現。還要注意,這個方法接受一些參數(MessageLocator bean的實例包含所有可用的消息) 標籤,通常保留給傳統的構造函數。
抽象工廠
最後一個,抽象的工廠設計模式,看起來類似於工廠方法。不同之處在於,我們可以將抽象工廠視爲這個詞的工業意義上的工廠,即。作爲提供所需對象的東西。工廠部件有:抽象工廠,抽象產品,產品和客戶。更準確地說,抽象工廠定義了構建對象的方法。抽象產品是這種結構的結果。產品是具有同樣結構的具體結果。客戶是要求創造產品來抽象工廠的人。
同樣的,在進入Spring的細節之前,我們將首先通過示例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"; } }
我們可以在這個例子中看到,抽象工廠封裝了對象的創建。對象創建可以使用與經典構造函數一樣使用的工廠方法模式。在Spring中,工廠的例子是org.springframework.beans.factory.BeanFactory。通過它的實現,我們可以從Spring的容器訪問bean。根據採用的策略,getBean方法可以返回已創建的對象(共享實例,單例作用域)或初始化新的對象(原型作用域)。在BeanFactory的實現中,我們可以區分: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的實例。它的抽象產物是一個對象。在我們的例子中,具體的產品就是被強轉爲ShoppingCart實例的抽象產品(Object)。在這裏,我們可以看到在Spring框架中使用解釋器,構建器,工廠方法和工廠。第一個是幫助解釋以SpEL表達的文本。三個最後的模式屬於創建設計模式的三劍客,它們在Spring中的主要目的是簡化對象的創建。他們通過分解複雜對象(構建器)的初始化或通過集中在公共點的初始化來做到對象的創建(要不然怎麼叫工廠呢,必須有通用點的)。