Spring框架中的設計模式(一)

知秋z發表於2017-09-24

設計模式有助於遵循良好的程式設計實踐。作為最流行的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的實現中,我們可以區分:ClassPathXmlApplicationContextXmlWebApplicationContextStaticWebApplicationContextStaticPortletApplicationContextGenericApplicationContextStaticApplicationContext

@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中的主要目的是簡化物件的建立。他們通過分解複雜物件(構建器)的初始化或通過集中在公共點的初始化來做到物件的建立(要不然怎麼叫工廠呢,必須有通用點的)。

相關文章