2013-09-25 67 views
2

問題主要是一個設計問題(與ddd有點相關)。對不起,關於這個人爲的例子:從同一基類的域類中調用不同的服務

假設,你有(域)類表示不同類型的水果:蘋果,櫻桃等。現在假設你必須實施一些壓制果汁的行爲。呼叫者應該能夠在不知道他得到哪個特定水果的情況下調用擠壓。

我應該把這種行爲放在哪裏?

當然,一個可以定義一個水果接口/基類的功能

Fruit#squeeze() 

,並讓所有子類實現自己的行爲。 現在主叫方可以簡單地做這樣的事情:

Fruit f = new Cherry(); 
f.squeeze(); 

但如果擠不那麼簡單,涉及到更復雜的行爲就像調用不同的外部服務,爲每一個水果不同的人喜歡

應該做什麼
AppleJuicerService#squeeze(Apple a) 

CherryJuicerService#squeeze(Cherry c) 

?從域類調用服務感覺不對。

我讀過關於雙調度模式,似乎不適合在這裏,因爲每個子類都需要不同的服務。

我的問題是:在這裏可以做些什麼來獲得「乾淨」的設計?

編輯:

感謝您的答案爲止。我會盡量澄清這個問題。我將嘗試給出另一個希望較少人爲的問題,例如我試圖在此處陳述的問題:

考慮允許將其內容顯示爲字符串的消息基類。

interface Message { 
    String showContent(); 
} 

現在假設我們有不同類型的消息等的EMailMessage:

class EMailMessage implements Message { 

    //some specific parameters for email 
    private EmailAddress recipientEmail; 

    public String showContent() { 
     //here the content would be converted to string 
     return "the content of an EMail" 
    } 
} 

另一種類型的將是一個SMSMessage:

class SMSMessage implement SMSMessage { 

    //some specific parameters for SMS 
    private TelNumber recepientTelephoneNumber; 

    public String showContent() { 
     //here the content would be converted to string 
     return "the content of a SMS" 
    } 
} 

另外假設,消息被建模爲實體,並且因此可以保存在數據庫中。儘管在技術上很有意思,但假設像Spring這樣的一些依賴注入框架被用來注入依賴關係。

類似於水果的例子,考慮我們必須實現發送消息給收件人的send()行爲。此外,假設發送電子郵件涉及與SMS不同的邏輯。現在,問題是:應該在哪裏發送發送消息的邏輯?

通常我會選擇創建一個用於發送SMS的服務,例如將封裝例如SMS服務提供商的API。此外,我會創建另一個服務來封裝發送電子郵件。

interface SendMessageService<T extends Message> { 
    void send(T message); 
} 

class SendEmailService extends SendMessageService<EMailMessage> { 
    public void send(EMailMessage message) { 
     //send the EMail 
    } 
} 

class SendSMSService extends SendMessageService<SMSMessage> { 
    public void send(SMSMessage message) { 
     //send the SMS 
    } 
} 

這種方法的缺點是,你不能沒有確定它的具體子類,即像下面發送一條消息不能直接

List<Message> messages = //Messages of different types 

SendMessageService service = //??? 

for (Message m : messages) { 
    service.send(m); 
} 

當然人們可以創建服務創建工廠根據消息的具體類型。但是這有點意味着克隆Message的繼承層次結構。有沒有更好的方法來達到預期的效果?或者我錯過了什麼?或者,以某種方式將服務注入實體會更好嗎?

+1

也許這個例子在這裏做得太過分了 - Fruit#squeeze()有一個void返回類型,它對Fruit有什麼作用? –

+1

+1如果您不解釋「擠壓」可能代表什麼以及爲什麼首先需要服務,我們無法提供一個好的答案。 (順便說一句,從一個實體中調用域服務是完全合法的,即使你會更頻繁地看到它) – guillaume31

+0

@ guillaume31我試圖編輯問題以進一步解釋問題 – John

回答

0

有一個定義標準行爲的基類Fruit。當你必須使用更復雜的實現時,你可以重寫適當的方法。

class Fruit { 
public void Squeeze(){ 
    // Standard squeeze behaviour 
} 
} 

class Apple extends Fruit { 
@Override 
public void Squeeze(){ 
    // Complex squeeze behaviour 
} 
} 

class Cherry extends Fruit { 
// Nothing special, cherries are easy to squeeze 
} 

如果您必須爲特定類型定義特定的實現,您將始終需要在某處定義行爲。如果這對於一種方法來說太多了,那麼你可以調用一個更詳細的類來爲你做。

你可以用一個工廠工作,做這樣的事情

class FruitManipulator { 
void Squeeze(Fruit f){ 
    // Switch over fruit, create new service depending on the type 
} 
} 

interface JuiceService<T extends Fruit> { 
void Squeeze(T f); 
} 

class AppleJuiceService implements JuiceService<Apple> { 
void Squeeze(Apple apple){ 
    // Do your thing 
} 
} 

而且使用這樣的:

FruitManipulator service = new FruitManipulator(); 
service.Squeeze(new Apple()); 

你可能想,雖然找到一個更好的例子:Squeeze()比喻ISN」易於使用。也許擴大擠壓實際上意味着什麼?

1

您可以將工作委派給SqueezeBehavior接口,並讓每個實施定義如何squeeze a Fruit或特定Fruit。這是一個很好的主意(這意味着它可以改進,但作爲第一步很好):

interface SqueezeBehavior<T> { 
    void squeeze(T squeezeMe); 
} 

interface FruitSqueezeBehavior<T extends Fruit> extends SqueezeBehavior<T> { 
} 

class FruitSqueezer implements FruitSqueezeBehavior<Fruit> { 
    public void squeeze(Fruit fruit) { 
     System.out.println("squizing any fruit"); 
    } 
} 

class AppleSqueezer implements FruitSqueezeBehavior<Apple> { 
    public void squeeze(Apple apple) { 
     System.out.println("squizing apple"); 
    } 
} 

class CherrySqueezer implements FruitSqueezeBehavior<Cherry> { 
    public void squeeze(Cherry cherry) { 
     System.out.println("squizing cherry"); 
    } 
} 

class FruitService { 

    public void foo(Fruit fruit) { 
     FruitSqueezeBehavior fruitSqueezer = ... 
     fruitSqueezer.squeeze(fruit); 
    } 
} 
+0

+1是否有任何方式得到它沒有編譯器警告? (沒有'SuppressWarnings') –

+0

謝謝,但你如何確定FruitService中FruitSqueezerBehavior的具體子類? (......) – John

+0

@John這取決於你的需求。通常,您可以從其他方法注入它,或者根據您的需要直接寫入它。 –

0

您可以考慮使用DomainEvents。這可以幫助你解耦外部服務(通常是無狀態的bean需要注入)的域模型

interface Fruit { 
    void squeeze(); 
} 

class Apple implements Fruit { 

    @Override 
    public void squeeze(){ 
     // domain rules validations 
     DomainEvents.raise(new AppleSequeezedEvent(this)); 
    } 
} 

class Cherry extends Fruit { 
    @Override 
    public void squeeze(){ 
     // domain rules validations 
     DomainEvents.raise(new CherrySequeezedEvent(this)); 
    } 
} 

class Banana extends Fruit { 
    @Override 
    public void squeeze(){ 
     // domain rules validations 
     // hmm...No one cares banana... 
    } 
} 

class DomainEvents { 
    private static List<DomainEventHandler> handlers = new ArrayList<DomainEventHandler>(); 

    public static void register(DomainEventHandler handler) { 
     this.handler.add(handler); 
    } 

    public static void raise(DomainEvent event) { 
     for (DomainEventHander handler: handlers) { 
      if (handler.subscribe(event.getClass()) { 
       handler.handle(event); 
      } 
     } 
    } 
} 

現在,當你測試的蘋果,你可以註冊一些處理程序模擬/存根:

@Test 
public void tellsAppleIsSqueezed() throws Throwable { 
    DomainEventHandler stub = new FruitSqueezedEventHandlerStub(Apple.class); 
    DomainEvents.register(stub); 

    Apple apple = new Apple(); 

    apple.squeeze(); 

    //assert state change of apple if any before you publishing the event 
    assertThat(stub.getSqueezed(), sameInstance(apple)); 
} 

您可以測試實際在他們自己的單元測試用例中。

但我認爲這個解決方案增加了額外的複雜性。