用于消除开关条件的干净代码(使用多态性)

正如 SOLID 原则所说,最好通过将开关条件转换为类和接口来消除它们。我想用这个代码来做:

注意:这段代码不是真正的代码,我只是把我的想法放进去了。

MessageModel message = getMessageFromAnAPI();
manageMessage(message);
...
void manageMessage(MessageModel message){        
    switch(message.typeId) {
        case 1: justSave(message); break;
        case 2: notifyAll(message); break;
        case 3: notify(message); break;
    }
}

现在我想删除开关语句。所以我为它创建了一些类,并尝试在这里实现多态性:

interface Message{
    void manageMessage(MessageModel message);
}
class StorableMessage implements Message{

    @Override
    public void manageMessage(MessageModel message) {
        justSave(message);
    }
}
class PublicMessage implements Message{

    @Override
    public void manageMessage(MessageModel message) {
        notifyAll(message);
    }
}
class PrivateMessage implements Message{

    @Override
    public void manageMessage(MessageModel message) {
        notify(message);
    }
}

然后我调用我的API来获取我的:MessageModel

MessageModel message = getMessageFromAnAPI();

现在我的问题来了。我有我的模型,我想用我的类来管理它。作为 SOLID 示例,我应该做这样的事情:

PublicMessage message = new Message();
message.manageMessage(message);

但是我怎么知道哪种类型与此消息相关,以便从它(或)或)中制作实例?!我应该再次把开关块放在这里做还是怎么做?PublicMessageStorableMessagePrivateMessage


答案 1

您可以执行以下操作:

static final Map<Integer,Consumer<MessageModel>> handlers = new HashMap<>();
static {
    handlers.put(1, m -> justSave(m));
    handlers.put(2, m -> notifyAll(m));
    handlers.put(3, m -> notify(m));
}

这将删除您的切换到

Consumer<Message> consumer = handlers.get(message.typeId);
if (consumer != null) { consumer.accept(message); }

集成操作分离原理

你当然应该封装这个:

class MessageHandlingService implements Consumer<MessageModel> {
    static final Map<Integer,Consumer<MessageModel>> handlers = new HashMap<>();
    static {
        handlers.put(1, m -> justSave(m));
        handlers.put(2, m -> notifyAll(m));
        handlers.put(3, m -> notify(m));
    }
    public void accept(MessageModel message) {
        Consumer<Message> consumer = handlers.getOrDefault(message.typeId, 
                m -> throw new MessageNotSupportedException());
        consumer.accept(message);
    }
}

使用您的客户代码

message = getMessageFromApi();
messageHandlingService.accept(message);

此服务是“集成”部分(与“实现”相反:cfg 集成操作隔离原则)。

使用 CDI 框架

对于具有 CDI 框架的生产环境,如下所示:

interface MessageHandler extends Consumer<MessageModel> {}
@Component
class MessageHandlingService implements MessageHandler {
    Map<Integer,MessageHandler> handlers = new ConcurrentHashMap<>();

    @Autowired
    private SavingService saveService;
    @Autowired
    private NotificationService notificationService;

    @PostConstruct
    public void init() {
        handlers.put(1, saveService::save);
        handlers.put(2, notificationService::notifyAll);
        handlers.put(3, notificationService::notify);
    }

    public void accept(MessageModel m) {  // as above }
}

可以在运行时更改行为

与@user7答案中的开关相比,这样做的优点之一是可以在运行时调整行为。你可以想象像这样的方法

public MessageHandler setMessageHandler(Integer id, MessageHandler newHandler);

这将安装给定的并返回旧的;例如,这将允许您添加装饰器。MessageHandler

一个有用的例子是,如果你有一个不可靠的Web服务提供处理;如果可以访问,则可以将其安装为句柄;否则,将使用默认处理程序。


答案 2

在这种情况下,可以使用工厂来获取 的实例。工厂将具有的所有实例,并根据 MessageModel 的 typeId 返回相应的实例。MessageMessage

class MessageFactory {
    private StorableMessage storableMessage;
    private PrivateMessage privateMessage;
    private PublicMessage publicMessage;
    //You can either create the above using new operator or inject it using some Dependency injection framework.

    public getMessage(MessageModel message) {
        switch(message.typeId) {
            case 1: return storableMessage; 
            case 2: return publicMessage;
            case 3: return privateMessage
            default: //Handle appropriately
        }
    }
}

调用代码将如下所示

MessageFactory messageFactory; //Injected 
...
MessageModel messageModel = getMessageFromAnAPI();

Message message = messageFactory.getMessage(messageModel);
message.manageMessage(messageModel);

如您所见,这并没有完全摆脱(并且您不需要因为使用switch本身并不坏)。SOLID 试图说的是,通过遵循 SRP(单一责任原则)和 OCP(开放-封闭原则)来保持代码整洁。这里的意思是,您的代码不应该具有在一个地方处理每个代码的实际处理逻辑switchtypeId

使用工厂,您已将创建逻辑移动到单独的位置,并且已将实际处理逻辑移动到相应的类。

编辑:只是重申 - 我的答案集中在OP的SOLID方面。通过具有单独的处理程序类(来自 OP 的实例),可以实现 SRP。如果其中一个处理程序类发生更改,或者当您添加新的消息 typeId ()(即,添加新的实现)时,您无需修改原始的处理程序类,因此可以实现 OCP。(假设其中每个都不包含琐碎的代码)。这些已经在OP中完成。Messagemessage.typeIdMessage

我在这里回答的真正要点是使用工厂来获得.这个想法是保持主应用程序代码的干净,并将开关,if/else和新运算符的使用限制为实例化代码。(类似于@Configuration类/在 Guice 中使用 Spring 或 Abstract 模块时实例化 Bean 的类)。OO原则并没有说使用开关是不好的。这取决于您在哪里使用它。在应用程序代码中使用它确实违反了SOLID原则,这就是我想带出来的。Message

我也喜欢从daniu@的想法,使用一种功能方式,同样甚至可以在上面的工厂代码中使用(或者甚至可以使用简单的Map来摆脱开关)。


推荐