2013-09-25 143 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)); 
} 

您可以测试实际在他们自己的单元测试用例中。

但我认为这个解决方案增加了额外的复杂性。