Sia*_*dos 14 java open-closed-principle solid-principles
正如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;
}
}
Run Code Online (Sandbox Code Playgroud)
现在我想删除switch语句.所以我为它创建了一些类,我尝试在这里实现一个多态:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后我调用我的API来获取我的MessageModel
:
MessageModel message = getMessageFromAnAPI();
Run Code Online (Sandbox Code Playgroud)
现在我的问题在这里.我有我的模型,我想用我的课来管理它.作为SOLID示例,我应该这样做:
PublicMessage message = new Message();
message.manageMessage(message);
Run Code Online (Sandbox Code Playgroud)
但是,我怎么知道哪个类型与此消息有关,以便从它(PublicMessage
或StorableMessage
或PrivateMessage
)创建一个实例?!我应该把开关块放在这里再做一次还是什么?
dan*_*niu 18
你可以这样做:
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));
}
Run Code Online (Sandbox Code Playgroud)
这将删除您的开关
Consumer<Message> consumer = handlers.get(message.typeId);
if (consumer != null) { consumer.accept(message); }
Run Code Online (Sandbox Code Playgroud)
你当然应该封装这个:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
使用您的客户端代码
message = getMessageFromApi();
messageHandlingService.accept(message);
Run Code Online (Sandbox Code Playgroud)
该服务是"集成"部分(与"实现"相对:cfg集成操作隔离原则).
对于具有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 }
}
Run Code Online (Sandbox Code Playgroud)
这与@ user7的答案中的切换相比的一个优点是可以在运行时调整行为.你可以想象像这样的方法
public MessageHandler setMessageHandler(Integer id, MessageHandler newHandler);
Run Code Online (Sandbox Code Playgroud)
哪个会安装给定的MessageHandler
并返回旧的; 例如,这将允许您添加装饰器.
一个有用的例子是,如果你有一个不可靠的Web服务提供处理; 如果它是可访问的,它可以作为handlelr安装; 否则,使用默认处理程序.
Tim*_*kle 10
这里的要点是您将实例化和配置与执行分开.
即使使用OOP,我们也无法避免使用if/else
级联或switch
语句来区分不同的情况.毕竟我们必须创建专门的具体类的实例.
但这应该是在初始化代码或某种工厂.
在业务逻辑中,我们希望通过在接口上调用泛型方法来避免if/else
级联或switch
语句,其中实现者更了解自己如何表现.
use*_*er7 10
在这种情况下,您可以使用工厂来获取实例Message
.工厂将拥有所有实例,Message
并根据MessageModel的typeId返回相应的实例.
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
调用代码看起来像
MessageFactory messageFactory; //Injected
...
MessageModel messageModel = getMessageFromAnAPI();
Message message = messageFactory.getMessage(messageModel);
message.manageMessage(messageModel);
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,这并没有switch
完全消除(你不需要因为使用开关本身并不坏).SOLID试图说的是通过遵循SRP(单一责任原则)和OCP(开放 - 封闭原则)来保持代码清洁.这意味着你的代码不应该在一个地方处理每个实际的处理逻辑typeId
.
在工厂中,您已将创建逻辑移动到一个单独的位置,并且您已将实际处理逻辑移动到相应的类.
编辑:
重申一下 - 我的回答集中在OP的SOLID方面.通过具有单独的处理程序类(Message
来自OP 的实例),您可以实现SRP.如果其中一个处理程序类发生更改,或者添加新消息typeId(message.typeId
)(即添加新Message
实现),则无需修改原始文件,从而实现OCP.(假设每个都不包含普通代码).这些已在OP中完成.
我在这里的答案的真正意义是使用工厂获得一个Message
.我们的想法是保持主应用程序代码的清洁,并将开关,if/else和new运算符的用法限制为实例化代码.(类似于@Configuration类/在Guice中使用Spring或Abstract模块时实例化Beans的类).OO原则并未说使用交换机是坏事.这取决于在那里你使用它.在应用程序代码中使用它确实违反了SOLID原则,这就是我想要提出的.
我也喜欢daniu @的想法,使用功能方式,甚至可以在上面的工厂代码中使用(或者甚至可以使用简单的Map来摆脱开关).