2010-01-11 58 views
7

我正在處理一組消息對象,每個消息對象都有一個對應於它們的唯一標識符。每條消息都可以從Map或ByteBuffer構造(消息是二進制的,但我們知道如何傳入和傳出二進制表示)。Java - 靜態工廠方法和開關語句

當前實現構建這些消息大致如下:現在

public static Message fromMap(int uuid, Map<String, Object> fields) { 
    switch (uuid) { 
     case FIRST_MESSAGE_ID: 
     return new FirstMessage(fields); 
     . 
     . 
     . 
     default: 
      // Error 
      return null; 
    } 
} 

public static Message fromByteBuffer(int uuid, ByteBuffer buffer) { 
    switch (uuid) { 
     case FIRST_MESSAGE_ID: 
     return new FirstMessage(buffer); 
     . 
     . 
     . 
     default: 
      // Error 
      return null; 
    } 
} 

,約1項Josh Bloch's Effective Java會談:考慮靜態工廠方法而不是構造器,這似乎是一個地方,這種模式是有用(客戶端不直接訪問消息子類型的構造函數;而是通過這種方法)。但我不喜歡這樣的事實,即我們必須記住更新兩個開關語句(違反DRY原則)。

我會很感激任何洞察到最好的方式來實現這一點;我們並沒有緩存對象(每次調用fromMap或fromByteBuffer都會返回一個新對象),這會否定使用這種靜態工廠方法的一些好處。關於這段代碼的一些東西讓我覺得不對,所以我很想聽到社區關於這是否構建新對象的有效方法的想法,或者如果不是更好的解決方案。

回答

12

也許你可以創建它的一個接口的MessageFactory和實現:

public interface MessageFactory { 
    Message createMessage(Map<String, Object> fields); 
    Message createMessage(ByteBuffer buffer); 
} 

public class FirstMessageFactory implements MessageFactory { 
    public Message createMessage(Map<String, Object> fields){ 
    return new FirstMessage(fields); 
    } 

    public Message createMessage(ByteBuffer buffer){ 
    return new FirstMessage(buffer); 
    } 

} 

接下來,在同一個類中的方法getFactoryFromId如上述方法:

public static MessageFactory getMessageFactoryFromId(int uuid){ 
switch (uuid) { 
    case FIRST_MESSAGE_ID: 
    return new FirstMessageFactory(); 
    ... 
    default: 
     // Error 
     return null; 
    } 
} 

但是不是這個,最好創建一個包含id和工廠的H​​ashmap,因此每次創建消息時都不必創建一個新的Factory對象。另請參閱comment below

和你的方法:

public static Message fromMap(int uuid, Map<String, Object> fields) { 
    getMessageFactoryFromId(uuid).createMessage(fields); 
} 

public static Message fromByteBuffer(int uuid, ByteBuffer buffer) { 
    getMessageFactoryFromId(uuid).createMessage(buffer); 
} 

這樣,您使用的是工廠模式,並沒有必要具有兩倍於switch語句。

(沒有測試這一點,所以可能有一些編譯錯誤/錯別字)

+0

如果工廠對象存儲在地圖中並由uuid檢索,則不需要開關。 – rsp 2010-01-11 19:48:15

+0

是的,這就是我說的:-)我甚至添加了一個鏈接到你的答案:) – Fortega 2010-01-12 09:18:13

+0

這是一個很好的解決方案(所以+1),但如果目標是擺脫雙開關,你可能只是因素將開關邏輯切換到單獨的方法。 (該地圖基本上是一個變相的開關) – wds 2010-01-16 00:05:08

0

你可以使用AbstractFactory模式,在那裏你會爲每個消息類型的工廠類,要麼通過將返回消息緩衝區或地圖。然後創建一個返回相應工廠對象的方法。然後從返回的工廠創建消息。

1

有沒有辦法將ByteBuffer轉換爲Map或其他東西?如果將輸入轉換爲標準化表單並應用唯一的開關,那將會很不錯。

如果你想要做的是獲得一個消息並用特定的值格式化它(比如「table:tableName沒有名爲colName的列」),你可以將ByteBuffer轉換爲一個Map並調用第一個方法。如果您需要新的msgId,則只擴展fromMap方法。

這就像分解公共部分一樣。

3

如果你有你的對象實現接口聲明的工廠方法,如:靜態嵌套類

public Message newInstance(Map<String, Object> fields); 
public Message newInstance(ByteBuffer buffer); 

,你的工廠可以創建的uuid索引的Map含工廠對象:

Map map = new HashMap(); 

map.put(Integer.valueOf(FirstMessage.UUID), new FirstMessage.Factory()); 

並通過地圖查找替換您的開關:

public static Message fromMap(int uuid, Map<String, Object> fields) { 

    Factory fact = map.get(Integer.valueOf(uuid)); 

    return (null == fact) ? null : fact.newInstance(fields); 
} 

public static Message fromByteBuffer(int uuid, ByteBuffer buffer) { 

    Factory fact = map.get(Integer.valueOf(uuid)); 

    return (null == fact) ? null : fact.newInstance(buffer); 
} 

這可以很容易地擴展到支持其他施工方法。

+0

只是一個小小的評論:你應該使用Integer.valueOf()而不是新的Integer()。 – I82Much 2010-01-11 18:47:39

+0

好點,改變了。 – rsp 2010-01-11 19:45:16

1

我建議使用枚舉類型有抽象方法,比如下面的例子:

enum MessageType { 

    FIRST_TYPE(FIRST_MESSAGE_ID) { 

     @Override 
     Message fromByteBuffer(ByteBuffer buffer) { 
      return new FirstMessage(buffer); 
     } 

     @Override 
     Message fromMap(Map<String, Object> fields) { 
      return new FirstMessage(fields); 
     } 

     @Override 
     boolean appliesTo(int uuid) { 
      return this.uuid == uuid; 
     } 

    }, 

    SECOND_TYPE(SECOND_MESSAGE_ID) { 

     @Override 
     Message fromByteBuffer(ByteBuffer buffer) { 
      return new SecondMessage(buffer); 
     } 

     @Override 
     Message fromMap(Map<String, Object> fields) { 
      return new SecondMessage(fields); 
     } 

     @Override 
     boolean appliesTo(int uuid) { 
      return this.uuid == uuid; 
     } 

    }; 

    protected final int uuid; 

    MessageType(int uuid) { 
     this.uuid = uuid; 
    } 

    abstract boolean appliesTo(int uuid); 

    abstract Message fromMap(Map<String, Object> map); 

    abstract Message fromByteBuffer(ByteBuffer buffer); 

} 

這樣,在現有的靜態方法,你可以簡單地做到這一點...

public static Message fromByteBuffer(int uuid, ByteBuffer buffer) { 
    Message rslt = null; 
    for (MessageType y : MessageType.values()) { 
     if (y.appliesTo(uuid)) { 
      rslt = y.fromByteBuffer(buffer); 
      break; 
     } 
    } 
    return rslt; 
} 

這種方法使您的靜態方法不必知道您支持的MessageType以及如何構建它們 - 您可以在不重構靜態方法的情況下添加,修改或刪除消息。

0

您應該抽象的FirstMessage對象:

public abstract Message { 
    // ... 
} 

然後在你的工廠緩存起來(而不是一個開關):

private static final Map<Integer, Class<Message>> MESSAGES = new HashMap<Integer, Class<Message>>(); 
static { 
    MESSAGES.put(1, FirstMessage.class); 
} 

在你的工廠方法:

public static Message fromMap(UUID uuid, Map<String, Object> fields) { 
    return MESSAGES.get(uuid).newInstance(); 
} 

無論如何,這只是一個想法,你必須做一些反射(讓構造函數)工作才能通過田野。

0

您可以修改Message,使其具有兩個初始化方法,一個用於Map,一個用於ByteBuffer(而不是兩個構造器版本)。然後你的工廠方法返回構造的(但未初始化的)消息,然後調用返回對象上的Map或ByteBuffer進行初始化。

所以,你現在有一個工廠方法是這樣的: -

private static Message createMessage(int uuid) { 
    switch (uuid) { 
     case FIRST_MESSAGE_ID: 
     return new FirstMessage(); 
     . 
     . 
     . 
     default: 
      // Error 
      return null; 
    } 

} 

,然後衆廠家方法成爲: -

public static Message fromMap(int uuid, Map<String, Object> fields) { 
    Message message = createMessage(uuid); 
    // TODO: null checking etc.... 
    return message.initialize(fields); 
} 

public static Message fromByteBuffer(int uuid, ByteBuffer buffer) { 
    Message message = createMessage(uuid); 
    // TODO: null checking etc.... 
    return message.initialize(buffer); 
} 
3

TEM 1:考慮靜態工廠方法而不是構造函數

您正在通過將構造函數隱藏在該工廠方法後面,因此不需要在此處添加其他工廠方法。

所以你可以用Factory界面和地圖來做到這一點。(基本上每個人都在說了,但有區別,你可以使用內部類的工廠內聯)

interface MessageFactory { 
    public Message createWithMap(Map<String,Object> fields); 
    public Message createWithBuffer(ByteBuffer buffer); 
} 

Map<MessageFactory> factoriesMap = new HashMap<MessageFactory>() {{ 
    put(FIRST_UUID, new MessageFactory() { 
     public Message createWithMap(Map<String, Object> fields) { 
      return new FirstMessage(fields); 
     } 
     public Message createWithBuffer(ByteBuffer buffer){ 
      return new FirstMessage(buffer); 
     } 
    }); 
    put(SECOND_UUID, new MessageFactory(){ 
     public Message createWithMap(Map<String, Object> fields) { 
      return new SecondMessage(fields); 
     } 
     public Message createWithBuffer(ByteBuffer buffer){ 
      return new SecondMessage(buffer); 
     } 
    }); 
    put(THIRD_UUID, new MessageFactory(){ 
     public Message createWithMap(Map<String, Object> fields) { 
      return new ThirdMessage(fields); 
     } 
     public Message createWithBuffer(ByteBuffer buffer){ 
      return new ThirdMessage(buffer); 
     } 
    }); 
    ... 
}}; 

而且你的調用會變成:

public static Message fromMap(int uuid, Map<String, Object> fields) { 
    return YourClassName.factoriesMap.get(uuid).createWithMap(fields); 
} 

public static Message fromByteBuffer(int uuid, ByteBuffer buffer) { 
    return YourClassName.factoriesMap.get(uuid).createWithBuffer(buffer); 
} 

因爲用於的uuid開關被用作工廠的鑰匙。

0

關於使用enum作爲策略的解決方案(將策略方法添加到枚舉中),clean代碼cheet sheet應用程序說這是一個可維護性的殺手。
Allthough我不知道爲什麼我想與你分享。

+0

有趣。但請將這些評論添加爲評論而不是答案。 – I82Much 2013-04-07 03:54:35