Google Prettify

2015-03-29

Design Pattern 102: Factory Method

這是factory method pattern的class diagram

之前有稍微對Design Pattern做了一些描述,大概說明了一下什麼是design pattern。今天會講解一下其中的一種:factroy methody...

factory method 主要是當需要產生複雜的物件的時候使用。 由於產生物件的過程複雜,封裝起來,讓使用物件的人不需要知道產生的過成,就可以取得這個物件。

沒什麼用的Hello World...

比方說我有一個書櫃的類別(BookShelf)。有書櫃就有,不過書並不是我們這次要講的重點,所以我們跳過…

回到書櫃這個類別。

書櫃的建構過程如下:

... 前面還有50行程式碼...
List<Book> books = bookDao.findBookBy(shelfId);
BookVo[] bookVos = new BookVo[books.size()];
... 假設這裡有100行程式碼...
BookShelf bookShelf = new BookShelf(bookVos);
Shelf shelf = shelfDao.findShelf(shelfId);
bookShelf.setShelf(shelf);

於是我用一個function(在Java 稱為method)將這麼多程式碼封裝起來,…

public class ShelfFactory {
    public BookShelf getBookShelf(String shelfId) {
        ...前面的150行程式碼...
        return bookShelf;
    }
}

於是當有人要取得BookShelf物件時,如果直接用new BookShelf()是行不通的,而且為了達到更好的封裝效果,建構子只有在相同一個package才能呼叫。 用戶端如果要取得BookShelf物件,只要使用可以很快速取得的ShelfFactory,就可以取得很裡雜的BookShelf物件…

... 前面只要2行可以取得context物件...
ShelfFactory boFactory = context.getBean(ShelfFactory.class);
BookShelf bookShelf = boFactory.getBookShelf("POMO");

眼尖的讀者,可能開始在猜context物件不會是Spring framework裡面的東西吧? 沒錯,就是你所想的那種東西。

通常來說Factory的function,都會使用static的宣告,因為Class是可以直接取得的

很常用的factory method pattern

前面的hello world用處不太大。 不過還記得我在之前的文章裡介紹,pattern 就是一再重複的東西,其實你時常在用,只是沒注意到。我舉一些例子…

Calendar calendar = Calendar.getInstance(); //取得Calendar 物件
Logger logger = LoggerFactory.getLogger(getClass()); //取得logger物件
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); //取得Spring context物件

這幾個物件,大家應該每天都在用,尤其是logger。

如果你有用過Spring 那你一定有用過

還有如果你有使用過Spring famework,你就一定有用過的一個factory method。 還記得上面那個看似沒用的hello world 裡,利用spring framework取得ShelfFactory 物件的部份嗎? 也是使用simple facotry pattern喔。

ShelfFactory boFactory = context.getBean(ShelfFactory.class); //其實spring 產生bean的過程是很裡複雜的

如果去trace Spring 的getBean跑最後,大概是這樣的一段,你說建構這樣一個bean會不會很複雜…

public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
    Assert.notNull(requiredType, "Required type must not be null");
    String[] beanNames = getBeanNamesForType(requiredType);
    if (beanNames.length > 1) {
        ArrayList<String> autowireCandidates = new ArrayList<String>();
        for (String beanName : beanNames) {
            if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
                autowireCandidates.add(beanName);
            }
        }
        if (autowireCandidates.size() > 0) {
            beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);
        }
    }
    if (beanNames.length == 1) {
        return getBean(beanNames[0], requiredType, args);
    }
    else if (beanNames.length > 1) {
        Map<String, Object> candidates = new HashMap<String, Object>();
        for (String beanName : beanNames) {
            candidates.put(beanName, getBean(beanName, requiredType, args));
        }
        String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
        if (primaryCandidate != null) {
            return getBean(primaryCandidate, requiredType, args);
        }
        String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
        if (priorityCandidate != null) {
            return getBean(priorityCandidate, requiredType, args);
        }
        throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
    }
    else if (getParentBeanFactory() != null) {
        return getParentBeanFactory().getBean(requiredType, args);
    }
    else {
        throw new NoSuchBeanDefinitionException(requiredType);
    }
}
getBean的宣告是:public T getBean(Class requiredType, Object... args) throws BeansException; 他的權限是purlic; method name 是getBean,需要傳入requiredType,指定回傳型別,以及一些其他所需要的參數…

什麼是封裝

小時候在學物件導向時,知道物件導向的三大特性:封裝、繼承、多型。 在講解封裝時沒什麼用的HelloWorld大概是這樣:

public BigDecimal getSalary() {
    return salary;
}

那時總沒什麼感覺,總在納悶為什麼這樣子的封裝會引起世界的潮流。 想來是那時真沒什麼實務經驗。 實務上封裝的應用,會是像上頭舉的Spring framework裡的getBean這樣。有了封裝,我們只要呼叫getBean這一個method,丟入所需要參數即可,不用花時間去了解這麼複雜的建構過程。

搭配應用

  1. Singleton
    Factory method通常會搭配Singleton一起應用。 上面的例子其實有搭配了singleton的1/4招─一半私有化(private)建構子─而且還不是完全私有化,因為使用的是
    package層級。

  2. Template method
    之前的例子,是回傳BookShelf物件,但我可以回傳Shelf物件。使用的人可以不用在意,取到的實例是BookShelf或是PromtionShelf

結語

Factory method是很簡單的一種pattern,他也可以算是其他應用的基礎。 如果以武功招式來說,他可以算是起手式。 大家要好好熟悉呀!

Factory method 算是一種起手式

更新

2015-05-02: 將原名稱為simple factory改為factory method.

沒有留言 :

您或許對這些有興趣

Related Posts with Thumbnails

最後

謝謝您的閱讀,希望您可以有豐富的收獲。