删除开关条件的干净代码(使用多态性)
Clean code for removing switch condition(using polymorphism)
正如 SOLID 原则所说,最好通过将它们转换为 类 和接口来删除开关条件。
我想用这段代码来做:
Note: This code is not real code and I just put my idea into it.
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;
}
}
现在我想删除 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);
}
}
然后我打电话给我的 API 得到我的 MessageModel
:
MessageModel message = getMessageFromAnAPI();
现在我的问题来了。我有我的模型,我想使用我的 类 来管理它。作为 SOLID 示例,我应该这样做:
PublicMessage message = new Message();
message.manageMessage(message);
但是我怎么知道哪种类型与此消息相关,以便从中创建实例(PublicMessage
或 StorableMessage
或 PrivateMessage
)?!我应该再次将 switch 块放在这里还是做什么?
这里的要点是将实例化和配置与执行分开。
即使使用 OOP,我们也无法避免使用 if/else
级联或 switch
语句来区分不同的情况。毕竟我们必须创建专门的具体实例 类.
但这应该在 initialization code 或某种 factory.
在业务逻辑中,我们希望通过在接口[=27]上调用泛型方法来避免if/else
级联或switch
语句=] 实施者自己更了解如何表现。
在这种情况下,您可以使用工厂来获取 Message
的实例。工厂将拥有 Message
的所有实例和 returns 基于 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
}
}
}
调用代码看起来像
MessageFactory messageFactory; //Injected
...
MessageModel messageModel = getMessageFromAnAPI();
Message message = messageFactory.getMessage(messageModel);
message.manageMessage(messageModel);
如您所见,这并没有完全摆脱 switch
(您不需要,因为使用 switch 本身并不坏)。 SOLID 试图说的是通过遵循 SRP(单一职责原则)和 OCP(Open-Closed 原则)来保持你的代码干净。这意味着您的代码不应该在一个地方为每个 typeId
处理 实际处理逻辑 。
对于工厂,您已将创建逻辑移至单独的位置,并且已将实际处理逻辑移至相应的 类。
编辑:
重申一下——我的回答集中在 OP 的 SOLID 方面。通过使用单独的处理程序 类(来自 OP 的 Message
的实例),您可以实现 SRP。如果其中一个处理程序 类 更改,或者当您添加新的消息类型 ID (message.typeId
)(即添加新的 Message
实现)时,您无需修改原始消息,因此您实现了OCP。 (假设其中每一个都不包含琐碎的代码)。这些已经在 OP 中完成了。
我在这里回答的真正要点是使用工厂来获得 Message
。这个想法是保持主要应用程序代码干净,并将开关、if/else 和新运算符的使用限制在实例化代码中。 (类似于 @Configuration 类/ 在使用 Spring 或 Guice 中的抽象模块时实例化 Beans 的 类)。 OO 原则不会 说使用开关不好。这取决于你在哪里使用它。在应用程序代码中使用它确实违反了 SOLID 原则,这就是我想要指出的。
我也喜欢 daniu@ 使用函数式方法的想法,甚至可以在上面的工厂代码中使用相同的方法(或者甚至可以使用简单的 Map 来摆脱开关)。
你可以这样做:
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);
此服务是 "integration" 部分(相对于 "implementation":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
和 return 旧的;例如,这将允许您添加装饰器。
一个有用的例子是,如果你有一个不可靠的网络服务提供处理;如果可以访问,则可以将其安装为处理程序;否则,将使用默认处理程序。
您可以使用Factory Pattern。我会添加一个具有值的枚举:
public enum MessageFacotry{
STORING(StoringMessage.TYPE, StoringMessage.class),
PUBLIC_MESSAGE(PublicMessage.TYPE, PublicMessage.class),
PRIVATE_MESSAGE(PrivateMessage.TYPE, PrivateMessage.class);
Class<? extends Message> clazz;
int type;
private MessageFactory(int type, Class<? extends Message> clazz){
this.clazz = clazz;
this.type = type;
}
public static Message getMessageByType(int type){
for(MessageFactory mf : values()){
if(mf.type == type){
return mf.clazz.newInstance();
}
}
throw new ..
}
}
然后您可以调用该枚举的静态方法并创建您要管理的 Message 的实例。
通常的简洁代码方法是让 MessageModel 包含其行为。
interface Message {
void manage();
}
abstract class MessageModel implements Message {
}
public class StoringMessage extends MessageModel {
public void manage() {
store();
}
}
public class NotifyingMessage extends MessageModel {
public void manage() {
notify();
}
}
你的 getMessageFromApi
然后 returns 是正确的类型,你的开关是
MessageModel model = getMessageFromApi();
model.manage();
这样一来,您实际上就在 getMessageFromApi()
方法中进行了切换,因为它必须决定生成哪条消息。
但是,这很好,因为它确实填充了消息类型 ID;并且客户端代码(您的交换机当前所在的位置)对消息的更改具有抵抗力;即添加另一种消息类型将被正确处理。
可以一起使用Factory pattern and Visitor pattern。
你可以这样创建一个工厂:
class MessageFactory {
public Message getMessage(MessageModel message) {
switch(message.typeId) {
case 1: return new StorableMessage((MessageModelType1) message);
case 2: return new PrivateMessage((MessageModelType2) message);
case 3: return new PublicMessage((MessageModelType3) message);
default: throw new IllegalArgumentException("unhandled message type");
}
}
}
并像这样声明您的消息:
interface Message {
void accept(Visitor visitor);
}
class StorableMessage implements Message {
private final MessageType1 message;
public StorableMessage(MessageModelType1 message) {
this.message = message;
}
@Override
public <Result> Result accept(Visitor<Result> visitor) {
return visitor.visit(this);
}
public MessageModelType1 getMessage() {
return message;
}
}
class PublicMessage implements Message {
...
}
class PrivateMessage implements Message {
...
}
并像这样声明一个 Visitor
:
interface Visitor {
void visit(StorableMessage message);
void visit(PublicMessage message);
void visit(PrivateMessage message);
}
并用此替换您的 switch 语句:
Message message = ....;
message.accept(new Visitor() {
@Override
public void visit(StorableMessage message) {
justSave(message.getMessage());
}
@Override
public void visit(PublicMessage message) {
notifyAll(message.getMessage());
}
@Override
public void visit(PrivateMessage message) {
notify(message.getMessage());
}
});
如果需要,您可以创建一个 class MessageModelFactory
并使用私有 Visitor
,而不是编写匿名 class。在那种情况下,将 Visitor
界面设置成这样可能会更好:
interface Visitor<Result> {
Result visit(StorableMessage message);
Result visit(PublicMessage message);
Result visit(PrivateMessage message);
}
您遇到的真正问题是 MessageModel
不是多态的。您需要将 MessageModel
转换为多态的 Message
class,但您不应将任何处理消息的逻辑放在 class 中。相反,它应该包含消息的实际内容,并使用访问者模式,如 所示,以便其他 classes 可以对 Message
进行操作。你不需要使用匿名 Visitor
;你可以创建像 MessageActionVisitor
.
这样的实现 classes
要将MessageModel
s 转换为各种Message
s,可以使用工厂,如 所示。除了选择 Message
到 return 的类型外,工厂还应使用 MessageModel
.
填写每个类型的 Message
字段。
正如 SOLID 原则所说,最好通过将它们转换为 类 和接口来删除开关条件。 我想用这段代码来做:
Note: This code is not real code and I just put my idea into it.
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;
}
}
现在我想删除 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);
}
}
然后我打电话给我的 API 得到我的 MessageModel
:
MessageModel message = getMessageFromAnAPI();
现在我的问题来了。我有我的模型,我想使用我的 类 来管理它。作为 SOLID 示例,我应该这样做:
PublicMessage message = new Message();
message.manageMessage(message);
但是我怎么知道哪种类型与此消息相关,以便从中创建实例(PublicMessage
或 StorableMessage
或 PrivateMessage
)?!我应该再次将 switch 块放在这里还是做什么?
这里的要点是将实例化和配置与执行分开。
即使使用 OOP,我们也无法避免使用 if/else
级联或 switch
语句来区分不同的情况。毕竟我们必须创建专门的具体实例 类.
但这应该在 initialization code 或某种 factory.
在业务逻辑中,我们希望通过在接口[=27]上调用泛型方法来避免if/else
级联或switch
语句=] 实施者自己更了解如何表现。
在这种情况下,您可以使用工厂来获取 Message
的实例。工厂将拥有 Message
的所有实例和 returns 基于 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
}
}
}
调用代码看起来像
MessageFactory messageFactory; //Injected
...
MessageModel messageModel = getMessageFromAnAPI();
Message message = messageFactory.getMessage(messageModel);
message.manageMessage(messageModel);
如您所见,这并没有完全摆脱 switch
(您不需要,因为使用 switch 本身并不坏)。 SOLID 试图说的是通过遵循 SRP(单一职责原则)和 OCP(Open-Closed 原则)来保持你的代码干净。这意味着您的代码不应该在一个地方为每个 typeId
处理 实际处理逻辑 。
对于工厂,您已将创建逻辑移至单独的位置,并且已将实际处理逻辑移至相应的 类。
编辑:
重申一下——我的回答集中在 OP 的 SOLID 方面。通过使用单独的处理程序 类(来自 OP 的 Message
的实例),您可以实现 SRP。如果其中一个处理程序 类 更改,或者当您添加新的消息类型 ID (message.typeId
)(即添加新的 Message
实现)时,您无需修改原始消息,因此您实现了OCP。 (假设其中每一个都不包含琐碎的代码)。这些已经在 OP 中完成了。
我在这里回答的真正要点是使用工厂来获得 Message
。这个想法是保持主要应用程序代码干净,并将开关、if/else 和新运算符的使用限制在实例化代码中。 (类似于 @Configuration 类/ 在使用 Spring 或 Guice 中的抽象模块时实例化 Beans 的 类)。 OO 原则不会 说使用开关不好。这取决于你在哪里使用它。在应用程序代码中使用它确实违反了 SOLID 原则,这就是我想要指出的。
我也喜欢 daniu@ 使用函数式方法的想法,甚至可以在上面的工厂代码中使用相同的方法(或者甚至可以使用简单的 Map 来摆脱开关)。
你可以这样做:
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);
此服务是 "integration" 部分(相对于 "implementation":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
和 return 旧的;例如,这将允许您添加装饰器。
一个有用的例子是,如果你有一个不可靠的网络服务提供处理;如果可以访问,则可以将其安装为处理程序;否则,将使用默认处理程序。
您可以使用Factory Pattern。我会添加一个具有值的枚举:
public enum MessageFacotry{
STORING(StoringMessage.TYPE, StoringMessage.class),
PUBLIC_MESSAGE(PublicMessage.TYPE, PublicMessage.class),
PRIVATE_MESSAGE(PrivateMessage.TYPE, PrivateMessage.class);
Class<? extends Message> clazz;
int type;
private MessageFactory(int type, Class<? extends Message> clazz){
this.clazz = clazz;
this.type = type;
}
public static Message getMessageByType(int type){
for(MessageFactory mf : values()){
if(mf.type == type){
return mf.clazz.newInstance();
}
}
throw new ..
}
}
然后您可以调用该枚举的静态方法并创建您要管理的 Message 的实例。
通常的简洁代码方法是让 MessageModel 包含其行为。
interface Message {
void manage();
}
abstract class MessageModel implements Message {
}
public class StoringMessage extends MessageModel {
public void manage() {
store();
}
}
public class NotifyingMessage extends MessageModel {
public void manage() {
notify();
}
}
你的 getMessageFromApi
然后 returns 是正确的类型,你的开关是
MessageModel model = getMessageFromApi();
model.manage();
这样一来,您实际上就在 getMessageFromApi()
方法中进行了切换,因为它必须决定生成哪条消息。
但是,这很好,因为它确实填充了消息类型 ID;并且客户端代码(您的交换机当前所在的位置)对消息的更改具有抵抗力;即添加另一种消息类型将被正确处理。
可以一起使用Factory pattern and Visitor pattern。
你可以这样创建一个工厂:
class MessageFactory {
public Message getMessage(MessageModel message) {
switch(message.typeId) {
case 1: return new StorableMessage((MessageModelType1) message);
case 2: return new PrivateMessage((MessageModelType2) message);
case 3: return new PublicMessage((MessageModelType3) message);
default: throw new IllegalArgumentException("unhandled message type");
}
}
}
并像这样声明您的消息:
interface Message {
void accept(Visitor visitor);
}
class StorableMessage implements Message {
private final MessageType1 message;
public StorableMessage(MessageModelType1 message) {
this.message = message;
}
@Override
public <Result> Result accept(Visitor<Result> visitor) {
return visitor.visit(this);
}
public MessageModelType1 getMessage() {
return message;
}
}
class PublicMessage implements Message {
...
}
class PrivateMessage implements Message {
...
}
并像这样声明一个 Visitor
:
interface Visitor {
void visit(StorableMessage message);
void visit(PublicMessage message);
void visit(PrivateMessage message);
}
并用此替换您的 switch 语句:
Message message = ....;
message.accept(new Visitor() {
@Override
public void visit(StorableMessage message) {
justSave(message.getMessage());
}
@Override
public void visit(PublicMessage message) {
notifyAll(message.getMessage());
}
@Override
public void visit(PrivateMessage message) {
notify(message.getMessage());
}
});
如果需要,您可以创建一个 class MessageModelFactory
并使用私有 Visitor
,而不是编写匿名 class。在那种情况下,将 Visitor
界面设置成这样可能会更好:
interface Visitor<Result> {
Result visit(StorableMessage message);
Result visit(PublicMessage message);
Result visit(PrivateMessage message);
}
您遇到的真正问题是 MessageModel
不是多态的。您需要将 MessageModel
转换为多态的 Message
class,但您不应将任何处理消息的逻辑放在 class 中。相反,它应该包含消息的实际内容,并使用访问者模式,如 Message
进行操作。你不需要使用匿名 Visitor
;你可以创建像 MessageActionVisitor
.
要将MessageModel
s 转换为各种Message
s,可以使用工厂,如Message
到 return 的类型外,工厂还应使用 MessageModel
.
Message
字段。