根据维基百科,在计算机编程中,策略模式(也称为策略模式)是一种行为软件设计模式,它使得能够在运行时选择算法。代码接收运行时指令来使用算法族中的哪个算法,而不是直接实现单个算法。
在现实生活中,这样的指令通常是对象字段,表示其特征并帮助选择所需的行为。策略模式的经典实现并不复杂,但Spring Boot将其提升到了一个新的水平。
让我们来看一下UML图:
我们有一个典型的模式示例。一个接口有多个实现和一个客户端类需要使用所有这些类型的行为。该接口包含两个方法:
public interface NotificationService {
void sendMessage(Recipient recipient, String message);
RecipientType getType();
}
第一个方法负责发送通知,而第二个方法将帮助我们选择适当的策略。接下来,我们可以根据所需的传递渠道实现此接口。让我们考虑以下三个:
通过SMTP协议发送通知的通道:
@Service
public class EmailService implements NotificationService {
@Override
public void sendMessage(Recipient recipient, String message) {
log.info("Sending message '{}' to {} ({})", message, recipient.getAddress(), recipient.getName());
}
@Override
public RecipientType getType() {
return RecipientType.EMAIL;
}
}
用于通知的通讯应用,例如Telegram:
@Service
public class TelegramService implements NotificationService {
@Override
public void sendMessage(Recipient recipient, String message) {
log.info("Sending message '{}' to {} ({})", message, recipient.getAddress(), recipient.getName());
}
@Override
public RecipientType getType() {
return RecipientType.TELEGRAM;
}
}
最后,用于通过移动网络进行通信的Sms服务:
@Service
public class SmsService implements NotificationService {
@Override
public void sendMessage(Recipient recipient, String message) {
log.info("Sending message '{}' to {} ({})", message, recipient.getAddress(), recipient.getName());
}
@Override
public RecipientType getType() {
return RecipientType.PHONE;
}
}
为了更好地理解,我们创建以下POJO类,以便能够创建具有多个接收者的分发组:
public class Destination {
private String name;
private Set<Recipient> recipients;
}
接收者类包括名称、通道类型和地址,可以表示为电子邮件地址、电话号码、通道ID等:
public class Recipient {
private String name;
private RecipientType type;
private String address;
}
我们还需要创建一个枚举来更方便地处理收件人类型:
public enum RecipientType {
TELEGRAM,
PHONE,
EMAIL
}
然后Spring Framework的魔力开始了。我们所需要做的就是创建一个NotificationServices列表,DI机制注入所有可用的实现。为了使代码更清晰,将此列表转换为具有RecipientType键的映射:
@Configuration
public class StrategyConfig {
private final List<NotificationService> notificationServices;
@Bean
public Map<RecipientType, NotificationService> buildStrategyMap() {
return notificationServices.stream()
.collect(Collectors.toMap(NotificationService::getType, Function.identity()));
}
}
为了测试算法,让我们根据域创建表:
CREATE TABLE destinations
(
id BIGINT NOT NULL,
destination_name VARCHAR(100),
CONSTRAINT segment_pk PRIMARY KEY (id)
);
create TABLE recipients
(
id BIGINT NOT NULL,
recipient_name VARCHAR(100),
destination_id BIGINT NOT NULL,
recipient_type VARCHAR(255),
address VARCHAR(255),
CONSTRAINT recipient_pk PRIMARY KEY (id),
CONSTRAINT destination_recipient_fk FOREIGN KEY (destination_id) REFERENCES destinations (id)
);
并准备测试数据:
insert into destinations(id, destination_name)
values (nextval('destination_id_generator'), 'Sales department'),
(nextval('destination_id_generator'), 'Development department');
insert into recipients(id, recipient_name, destination_id, recipient_type, address)
values (nextval('recipient_id_generator'), 'Nick',
(select id from destinations where destination_name = 'Sales department'), 'PHONE', '777-77-77'),
(nextval('recipient_id_generator'), 'John',
(select id from destinations where destination_name = 'Sales department'), 'PHONE', '777-77-78'),
(nextval('recipient_id_generator'), 'Theresa',
(select id from destinations where destination_name = 'Sales department'), 'TELEGRAM', '-739180421'),
(nextval('recipient_id_generator'), 'Michael',
(select id from destinations where destination_name = 'Sales department'), 'EMAIL', 'manager@company.com'),
(nextval('recipient_id_generator'), 'Kristina',
(select id from destinations where destination_name = 'Development department'), 'TELEGRAM', '-739180429'),
(nextval('recipient_id_generator'), 'Denis',
(select id from destinations where destination_name = 'Development department'), 'EMAIL', 'lead@company.com');
最后一步也是最令人印象深刻的是客户端代码。在这里,我们注入通知服务映射,然后我们所需要做的就是遍历目的地和收件人,获取适当的策略并将我们的通知发送给收件人:
@Component
public class NotificationController {
private final Map<RecipientType, NotificationService> notificationServices;
private final DestinationService destinationService;
@Scheduled(cron = "0 * * ? * *")
public void sendOutNotifications() {
List<Destination> destinations = destinationService.getDestinations();
String message = checkAnomalies();
destinations.forEach(destination -> destination.getRecipients()
.forEach(recipient -> notificationServices.get(recipient.getType())
.sendMessage(recipient, message)));
}
private String checkAnomalies() {
// checking metrics
return "Some results of checking";
}
}
为了测试整个过程,让我们使用调度程序,每分钟运行一次,检查指标并发送消息。如果我们正确配置了一切,我们将得到以下结果:
2023-05-15T18:51:32.539+03:00 INFO 28936 --- [nio-8081-exec-1] com.kalita.services.impl.SmsService : Sending message 'Some important info' to 777-77-78 (John)
2023-05-15T18:51:32.539+03:00 INFO 28936 --- [nio-8081-exec-1] c.kalita.services.impl.MessengerService : Sending message 'Some important info' to -739180421 (Theresa)
2023-05-15T18:51:32.539+03:00 INFO 28936 --- [nio-8081-exec-1] com.kalita.services.impl.SmsService : Sending message 'Some important info' to 777-77-77 (Nick)
2023-05-15T18:51:32.539+03:00 INFO 28936 --- [nio-8081-exec-1] com.kalita.services.impl.EmailService : Sending message 'Some important info' to manager@company.com (Michael)
2023-05-15T18:51:32.539+03:00 INFO 28936 --- [nio-8081-exec-1] c.kalita.services.impl.MessengerService : Sending message 'Some important info' to -739180429 (Kristina)
2023-05-15T18:51:32.539+03:00 INFO 28936 --- [nio-8081-exec-1] com.kalita.services.impl.EmailService : Sending message 'Some important info' to lead@company.com (Denis)
这个例子向我们展示了如何使用这个模式,利用Spring的力量来实现结果。当我们开发不同的支付服务或将不同的API集成到类似的逻辑中时,也可以应用这种模式。最后但并非最不重要的是,如果我们想添加新的行为算法,我们只需要实现接口并创建一个新的枚举字段。无需更改调用者代码。
译自:https://medium.com/@inginiir/the-strategy-pattern-is-simple-and-clean-211d344249b0
评论(0)