2

该问题主要是设计问题(与 ddd 有点相关)。很抱歉这个人为的例子:

假设,您有代表不同类型水果的(域)类:苹果、樱桃等。现在假设你必须实现一些压榨果汁的行为。调用者应该能够在不知道他得到哪种特定水果的情况下调用挤压。

我应该把这种行为放在哪里?

当然,可以定义一个水果接口/基类函数

Fruit#squeeze()

并让所有子类实现自己的行为。现在调用者可以简单地做这样的事情:

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

但是,如果挤压不是那么简单并且涉及更复杂的行为,比如调用不同的外部服务,每个水果都有不同的服务,应该怎么办?

AppleJuicerService#squeeze(Apple a)

CherryJuicerService#squeeze(Cherry c)

? 从域类调用服务感觉不对。

我读过似乎不适合这里的双重调度模式,因为每个子类都需要不同的服务。

我的问题是:在这里可以做些什么来获得“干净”的设计?

编辑:

感谢您迄今为止的所有回答。我会试着澄清一下这个问题。对于我在这里要说明的问题,我将尝试给出另一个希望不那么做作的示例:

考虑一个 Message 基类,它允许将其内容显示为字符串。

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 的继承层次结构。有没有更好的方法来达到预期的结果?还是我错过了什么?或者以某种方式将服务注入实体会更好吗?

4

3 回答 3

1

您可以将工作委托给一个SqueezeBehavior接口,并让每个实现定义squeeze一个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);
    }
}
于 2013-09-25T21:10:42.683 回答
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()类比并不容易使用。也许扩大挤压实际上意味着什么?

于 2013-09-25T20:58:25.607 回答
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));
}

您可以在自己的单元测试用例中测试真正的处理程序。

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

于 2013-09-26T02:02:21.317 回答