簡體   English   中英

Generics 並在運行時選擇正確的接口實現

[英]Generics and selecting correct interface implementation at runtime

我正在研究 ES & CQRS 系統的 PoC。

我定義了以下類來表示命令和事件,這些命令和事件代表正在處理的命令的 output

public class CreateEstateCommand extends Command {}

public class ChangeEstateOwnerCommand extends Command {}

public class EstateCreatedEvent extends DomainEvent {}

public class EstateOwnerChangedEvent extends DomainEvent {}

這些命令在實現以下接口的類中處理

/**
 * Specific command handlers define what logic should be carried out during handling a command of type C.
 * Single command execution results in an outcome of domain event of type E
 */
public interface CommandHandler<C extends Command, E extends DomainEvent> {
  E handleCommand(C command);
}

public class EstateCreatedCommandHandler implements CommandHandler<CreateEstateCommand, EstateCreatedEvent> {
    @Override
    public EstateCreatedEvent handleCommand(CreateEstateCommand command) { /***/ }
}

public class ChangeEstateOwnerCommandHandler implements CommandHandler<ChangeEstateOwnerCommand, EstateOwnerChangedEvent> {
    @Override
    public EstateOwnerChangedEvent handleCommand(ChangeEstateOwnerCommand command) { /***/ }
}

現在是我想使用這些特定處理程序的部分。 命令處理的流程可以表示如下:

命令通過API進入系統,轉發給CommandServce class處理

public class CommandService {

    private final EventService eventService;
    private final CommandGateway commandGateway;

    public void handleCommand(CreateEstateCommand command) {
        EstateCreatedEvent event = commandGateway.handleCommand(command);
        eventService.handleEvent(event);
    }

    public void handleCommand(ChangeEstateOwnerCommand command) {
        EstateOwnerChangedEvent event = commandGateway.handleCommand(command);
        eventService.handleEvent(event);
    }
}

如您所見, handleCommand()方法對於每個提交的命令都是重復的。 這背后的原因是我在運行時選擇適當的處理程序實現時遇到的問題,具體取決於Command.commandType

@Service
public class CommandGateway {

    private final Map<String, CommandHandler<?, ?>> commandHandlers;

    @Autowired
    public CommandGateway(Map<String, CommandHandler<?, ?>> commandHandlers) {
        this.commandHandlers = commandHandlers;
    }

    public EstateCreatedEvent handleCommand(CreateEstateCommand command) {
        EstateCreatedCommandHandler handler = (EstateCreatedCommandHandler) commandHandlers.get(command.getCommandType());
        return handler.handleCommand(command);
    }


    public EstateOwnerChangedEvent handleCommand(ChangeEstateOwnerCommand command) {
        ChangeEstateOwnerCommandHandler handler = (ChangeEstateOwnerCommandHandler) commandHandlers.get(command.getCommandType());
        return handler.handleCommand(command);
    }

}

上面的片段是我無法概括的部分。 是否有可能實現CommandGateway class,所以CommandService可以如下所示:

public class CommandService {
    
    public <C extends Command, E extends DomainEvent> void handleCommand(C command) {
        E event = commandGateway.handleCommand(command);
    }
}

並提供類型安全的對象?

根本問題是 map,其值是通配符類型的,即實際上沒有類型,更具體地說,沒有鍵入以與鍵對齊。

您已經通過信任注入映射的條目而破壞了一些類型的安全性,因此只需使用原始的CommandHandler ,它將接受任何命令,並使用未經檢查的強制轉換以獲得正確鍵入的返回值:

@SuppressWarnings({"unchecked", "rawtypes"})
public <C extends Command, E extends DomainEvent> E handleCommand(C command) {
    CommandHandler handler =  commandHandlers.get(command.getCommandType());
    return (E)handler.handleCommand(command);
}

@SuppressWarnings添加了這樣您的 IDE 和構建抱怨。

雖然這看起來很殘酷,但實際上您並沒有失去任何類型安全性。 當您像您一樣鍵入 map 時,這會丟失,不幸的是,鑒於 map 鍵入不會將值類型綁定到鍵類型,這是不可避免的。

如果你做這樣的事情怎么辦:

static abstract class Command {

    public abstract String getCommandType();

    public abstract Class<? extends DomainEvent> type();
}

你的實現:

public class CreateEstateCommand extends Command {
    @Override
    public String getCommandType() {
        return null; // whatever here
    }

    @Override
    public Class<EstateCreatedEvent> type() {
        return EstateCreatedEvent.class;
    }
}

用法是:

public DomainEvent handleCommand(Command command) {
    return command.type().cast(commandHandlers.get(command.getCommandType()));
}

使用 generics 實現這種端到端的困難在於我們將運行時類型決策編譯時類型檢查混合在一起。 您的設計接近於 MVC 設計,其中處理程序是在運行時檢索的。 因此,在編譯時只能進行有限的類型安全檢查。

讓我們看一個流程:

  • API 接收到帶有其輸入的命令
  • 您可能有一個特定的 controller 方法,該方法知道命令及其輸入,並且可以構造CreateEstateCommandChangeEstateOwnerCommand
  • 現在,在運行時,我們必須訪問 map 以了解關聯的CommandHandler 現在,我們必須在運行時執行此操作的真正原因是,由於使用Map的設計,無法在編譯時做出決定。

如果您在CommandGateway中有以下方法,則永遠無法確定從服務 class 傳遞的Command實例是否真的是映射的CommandHandler想要的特定子項。 因此,它不會編譯。

public DomainEvent handleCommand( Command command ) {
    CommandHandler<? extends Command, ? extends DomainEvent> cmdHandler = commandHandlers.get(command.getCommandType());
    return cmdHandler.handleCommand( command );
}

因此,您必須取消端到端類型安全要求或為您提供CommandHandler實例的“工廠”樣式 map。 省略后者將意味着 API 方法一開始就知道它需要哪個CommandHandler

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM