spring框架中的設計模式一

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匀们,StaticWebApplicationContextStaticPortletApplicationContext准给,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)建软驰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市心肪,隨后出現(xiàn)的幾起案子锭亏,更是在濱河造成了極大的恐慌,老刑警劉巖硬鞍,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慧瘤,死亡現(xiàn)場離奇詭異,居然都是意外死亡固该,警方通過查閱死者的電腦和手機锅减,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伐坏,“玉大人怔匣,你說我怎么就攤上這事¤氤粒” “怎么了每瞒?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵金闽,是天一觀的道長。 經(jīng)常有香客問我剿骨,道長代芜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任浓利,我火速辦了婚禮挤庇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贷掖。我一直安慰自己罚随,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布羽资。 她就那樣靜靜地躺著淘菩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屠升。 梳的紋絲不亂的頭發(fā)上潮改,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音腹暖,去河邊找鬼汇在。 笑死,一個胖子當著我的面吹牛脏答,可吹牛的內(nèi)容都是我干的糕殉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼殖告,長吁一口氣:“原來是場噩夢啊……” “哼阿蝶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起黄绩,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤羡洁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后爽丹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筑煮,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年粤蝎,在試婚紗的時候發(fā)現(xiàn)自己被綠了真仲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡初澎,死狀恐怖秸应,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤灸眼,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布卧檐,位于F島的核電站,受9級特大地震影響焰宣,放射性物質(zhì)發(fā)生泄漏霉囚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一匕积、第九天 我趴在偏房一處隱蔽的房頂上張望盈罐。 院中可真熱鬧,春花似錦闪唆、人聲如沸盅粪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽票顾。三九已至,卻和暖如春帆调,著一層夾襖步出監(jiān)牢的瞬間奠骄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工番刊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留含鳞,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓芹务,卻偏偏與公主長得像蝉绷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子枣抱,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360