Лучшие подпрограммы шаблонов проектирования — используйте шаблоны стратегии с удовольствием

Шаблоны проектирования
Лучшие подпрограммы шаблонов проектирования — используйте шаблоны стратегии с удовольствием

Автор|Чжоу Ми (The Leaf)

Паттерн стратегии определяет набор стратегий, которые инкапсулированы в разные классы, и каждая стратегия может быть заменена друг другом в соответствии с текущим сценарием, так что изменение стратегии может быть независимым от оператора. Например, когда мы хотим куда-то поехать, мы выбираем разные способы передвижения (совместное использование велосипедов, поездка на автобусе, такси Диди и т. д.) в зависимости от расстояния (или в зависимости от текущей экономической ситуации). .

Когда использовать шаблон стратегии

Ali Development Protocol — Programming Protocol — Control Statement — Статья 6: Код логического суждения if-else с более чем 3 уровнями может быть реализован с использованием защитного оператора, режима стратегии, режима состояния и т. д. Я думаю, что все видели этот код:

if (conditionA) {
    逻辑1
} else if (conditionB) {
    逻辑2
} else if (conditionC) {
    逻辑3
} else {
    逻辑4
}

Хотя этот код прост в написании, он явно нарушает два основных принципа объектной ориентации:

  • Принцип единой ответственности (у класса должна быть только одна причина для изменения): поскольку любая логика изменяется позже, текущий класс будет изменен

  • Принцип открытости-закрытости (открыт для расширения, закрыт для модификации): если в это время необходимо добавить (удалить) логику, изменение исходного кода неизбежно.

Поскольку два вышеупомянутых принципа нарушаются, особенно когда объем кода в блоке if-else относительно велик, расширение и поддержка последующего кода постепенно становятся очень сложными и подверженными ошибкам, а использование защитных операторов не может избежать над двумя. проблема. Итак, основываясь на своем опыте, я придумал практику, которую лично считаю лучшей:

  • If-else не превышает 2-х слоев, код в блоке 1~5 строк, напрямую прописанный в блоке, в противном случае он инкапсулируется как метод

  • Если-else имеет более 2-х слоев, но код в блоке не превышает 3-х строк, попробуйте использовать сторожевые операторы

  • Если-иначе превышает 2 слоя, а код в блоке превышает 3 строки, попробуйте использовать режим стратегии

Получайте удовольствие, используя режим стратегии

В Spring существуют различные способы реализации шаблона стратегии.Позвольте мне поделиться моим текущим «лучшим способом» реализации шаблона стратегии (если у вас есть лучший способ, пожалуйста, просветите меня и обсудите его вместе).

фон спроса

Динамические формы для нашей платформы, ранее предназначенные для ввода данных модели. Теперь бизнес-сторона надеется открыть возможности формы, которые можно использовать не только для представления модели, но и для представления заданных функций бизнес-стороны (метод предназначен для привязки обобщенного сервиса HSF, HSF является Структура RPC внутри Департамента Дао). В дополнение к отправке в «режиме предварительного просмотра», когда мы настраиваем форму, форма в настоящее время имеет следующие три типа отправки:

  • Отправить во время предварительного просмотра формы

  • Фиксация при вводе модели

  • Зафиксировать при привязке HSF

А вот и мой «лучший распорядок дня».

Первым шагом является определение интерфейса политики.

Сначала определите интерфейс стратегии, включая два метода:

1. Как получить тип полиса

2. Методы обработки логики стратегии

/**
 * 表单提交处理器
 */
public interface FormSubmitHandler<R extends Serializable> {

    /**
     * 获得提交类型(返回值也可以使用已经存在的枚举类)
     *
     * @return 提交类型
     */
    String getSubmitType();

    /**
     * 处理表单提交请求
     *
     * @param request 请求
     * @return 响应,left 为返回给前端的提示信息,right 为业务值
     */
    CommonPairResponse<String, R> handleSubmit(FormSubmitRequest request);
}
/**
 * 表单提交的请求
 */
@Getter
@Setter
public class FormSubmitRequest {

    /**
     * 提交类型
     *
     * @see FormSubmitHandler#getSubmitType()
     */
    private String submitType;

    /**
     * 用户 id
     */
    private Long userId;

    /**
     * 表单提交的值
     */
    private Map<String, Object> formInput;

    // 其他属性
}

Среди них метод getSubmitType класса FormSubmitHandler используется для получения типа отправки формы (то есть типа политики), который используется для непосредственного получения соответствующей реализации политики в соответствии с переданными клиентом параметрами; клиентом инкапсулируются как FormSubmitRequest и передаются в handleSubmit для обработки.

Второй шаг – реализация соответствующих стратегий.

Отправить во время предварительного просмотра формы

@Component
public class FormPreviewSubmitHandler implements FormSubmitHandler<Serializable> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public String getSubmitType() { return "preview"; }

    @Override
    public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) {
        logger.info("预览模式提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());

        return CommonPairResponse.success("预览模式提交数据成功!", null);
    }
}

Фиксация при вводе модели

@Component
public class FormModelSubmitHandler implements FormSubmitHandler<Long> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public String getSubmitType() { return "model"; }

    @Override
    public CommonPairResponse<String, Long> handleSubmit(FormSubmitRequest request) {
        logger.info("模型提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());

        // 模型创建成功后获得模型的 id
        Long modelId = createModel(request);

        return CommonPairResponse.success("模型提交成功!", modelId);
    }

    private Long createModel(FormSubmitRequest request) {
        // 创建模型的逻辑
        return 123L;
    }
}

Подача схемы HSF

@Component
public class FormHsfSubmitHandler implements FormSubmitHandler<Serializable> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public String getSubmitType() { return "hsf"; }

    @Override
    public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) {
        logger.info("HSF 模式提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());

        // 进行 HSF 泛化调用,获得业务方返回的提示信息和业务数据
        CommonPairResponse<String, Serializable> response = hsfSubmitData(request);

        return response;
    }

    ...
}

Шаг 3. Создайте простую фабрику стратегии

@Component
public class FormSubmitHandlerFactory implements InitializingBean, ApplicationContextAware {

    private static final
    Map<String, FormSubmitHandler<Serializable>> FORM_SUBMIT_HANDLER_MAP = new HashMap<>(8);

    private ApplicationContext appContext;

    /**
     * 根据提交类型获取对应的处理器
     *
     * @param submitType 提交类型
     * @return 提交类型对应的处理器
     */
    public FormSubmitHandler<Serializable> getHandler(String submitType) {
        return FORM_SUBMIT_HANDLER_MAP.get(submitType);
    }

    @Override
    public void afterPropertiesSet() {
        // 将 Spring 容器中所有的 FormSubmitHandler 注册到 FORM_SUBMIT_HANDLER_MAP
        appContext.getBeansOfType(FormSubmitHandler.class)
                  .values()
                  .forEach(handler -> FORM_SUBMIT_HANDLER_MAP.put(handler.getSubmitType(), handler));
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
        appContext = applicationContext;
    }
}

Мы позволяем FormSubmitHandlerFactory реализовать интерфейс InitializingBean.В методе afterPropertiesSet все FormSubmitHandler автоматически регистрируются в FORM_SUBMIT_HANDLER_MAP на основе контейнера Spring, поэтому после запуска контейнера Spring метод getHandler может напрямую получить соответствующий обработчик отправки формы через submitType.

Шаг 4, используйте и тестируйте

В службе форм мы получаем соответствующий обработчик отправки формы через FormSubmitHandlerFactory для обработки различных типов отправки:

@Service
public class FormServiceImpl implements FormService {

    @Autowired
    private FormSubmitHandlerFactory submitHandlerFactory;

    public CommonPairResponse<String, Serializable> submitForm(@NonNull FormSubmitRequest request) {
        String submitType = request.getSubmitType();

        // 根据 submitType 找到对应的提交处理器
        FormSubmitHandler<Serializable> submitHandler = submitHandlerFactory.getHandler(submitType);

        // 判断 submitType 对应的 handler 是否存在
        if (submitHandler == null) {
            return CommonPairResponse.failure("非法的提交类型: " + submitType);
        }

        // 处理提交
        return submitHandler.handleSubmit(request);
    }
}

Фабрика отвечает только за получение обработчиков, обработчики отвечают только за обработку конкретных представлений, а служба отвечает только за логическую организацию, чтобы достичь функциональной «низкой связанности и высокой связности».

Напишите простой контроллер:

@RestController
public class SimpleController {

    @Autowired
    private FormService formService;

    @PostMapping("/form/submit")
    public CommonPairResponse<String, Serializable> submitForm(@RequestParam String submitType,
                                                               @RequestParam String formInputJson) {
        JSONObject formInput = JSON.parseObject(formInputJson);

        FormSubmitRequest request = new FormSubmitRequest();
        request.setUserId(123456L);
        request.setSubmitType(submitType);
        request.setFormInput(formInput);

        return formService.submitForm(request);
    }
}

Наконец, простой тест:

Я чувствую это, это очень приятное ощущение~

Представьте себе расширение

Если нам нужно добавить новую стратегию, например привязать отправку функции FaaS, нам просто нужно добавить новую реализацию стратегии:

@Component
public class FormFaasSubmitHandler implements FormSubmitHandler<Serializable> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public String getSubmitType() { return "faas"; }

    @Override
    public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) {
        logger.info("FaaS 模式的提交:userId={}, formInput={}", request.getUserId(), request.getFormInput());

        // 进行 FaaS 函数调用,并获得业务方返回的提示信息和业务数据
        CommonPairResponse<String, Serializable> response = faasSubmitData(request);

        return response;
    }

    ... 
}

В настоящее время нет необходимости изменять какой-либо код, потому что FormFaasSubmitHandler будет автоматически зарегистрирован в FormSubmitHandlerFactory при перезапуске контейнера Spring — программирование, ориентированное на Spring, слишком мило~