首页
Preview

策略模式简单清晰。

根据维基百科,在计算机编程中,策略模式(也称为策略模式)是一种行为软件设计模式,它使得能够在运行时选择算法。代码接收运行时指令来使用算法族中的哪个算法,而不是直接实现单个算法。

在现实生活中,这样的指令通常是对象字段,表示其特征并帮助选择所需的行为。策略模式的经典实现并不复杂,但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

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
阿波
The minute I see you, I want your clothes gone!

评论(0)

添加评论