Вынужден рефакторить код, на этот раз я убил if-else

Java
Вынужден рефакторить код, на этот раз я убил if-else

Эта статья размещена в личном блоге:www.chengxy-nds.top, совместное использование технических ресурсов и совместный прогресс

Кажется, компания недавно собрала деньги! Я как сумасшедший начал искать каналы для продвижения, и теперь я, наконец, понимаю, почему я агрессивно набирал людей в предыдущем пункте. Это оказалась очередная большая игра, что хорошо для сотрудников в целом. поднимите начальство.На тему зарплаты.

Однако повышения зарплаты пока не последовало, а спрос на одну машину за другой есть.Каждый день подключаются новые каналы, и каждый канал должен оказывать персонифицированную поддержку, и объем разработки резко увеличился. В последнее время у меня было не так много времени, чтобы публиковать сообщения, а уйти с работы вовремя — это роскошь!

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

традиционная реализация

Посмотрим на псевдокод ниже, это примерно код логики ордера до рефакторинга, так как исходников мало, то сделать простоif-elseЛогического суждения достаточно для удовлетворения потребностей.

Сейчас логика обработки каждого источника заказа имеет сотни строк кода, который выглядит раздутым, но я еще не начал рефакторинг.古董Уровень кода, все еще хочу быть в безопасности.

Но в этот раз сразу добавились десятки источников, и поддерживать его таким образом уже невозможно.Представьте себе такой раздутыйif-elseКод, не говоря уже о разработке, слишком велик, чтобы о нем думать!

public class OrderServiceImpl implements IOrderService {
    @Override
    public String handle(OrderDTO dto) {
        String type = dto.getType();
        if ("1".equals(type)) {
            return "处理普通订单";
        } else if ("2".equals(type)) {
            return "处理团购订单";
        } else if ("3".equals(type)) {
            return "处理促销订单";
        }
        return null;
    }
}

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

Подумав о реконструкции на основе текущего бизнес-сценария, более целесообразно использовать режим стратегии, которыйoopОдин из наиболее известных шаблонов проектирования в , абстракция поведения метода.

Шаблон стратегии определяет семейство алгоритмов с общим поведением, каждый из которых инкапсулирован, может заменяться друг другом и изменяется независимо от клиента.

Во-первых, использование режима стратегии:

  • Существует несколько способов решения одной и той же проблемы, только когда конкретное поведение отличается;
  • Когда необходимо безопасно инкапсулировать несколько операций одного типа;
  • Один и тот же абстрактный класс имеет несколько подкатегорий, и клиенты должны использоватьif-elseилиswitch-caseдля выбора определенного подкласса.

Это модифицированный код, использующий режим стратегии:

@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{

 @Autowired
 private OrderStencilledService orderStencilledService;
 
 @Override
 public void handle(OrderBO orderBO) {
  
    //订单完结广播通知(1 - 支付完成)
     orderStencilledService.dispatchModeFanout(orderBO);
  
     // SCMS 出库单
     orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());
 }
}

Каждый источник заказа имеет свой отдельный класс реализации логики, и каждый раз, когда вам нужно добавить источник заказа, создайте новый класс реализации напрямую, измените@OrderHandlerType(16)Значение достаточное, и нет необходимости переворачивать вонючую и долгоif-lese.

Мало того, что при постановке задач каждый человек отвечает за разработку нескольких исходных логик ордера, которые не могут мешать друг другу, и значительно снижают конфликтность объединенного кода.

Во-вторых, конкретный процесс реализации:

1. Определите аннотации

Определите аннотацию, которая идентифицирует источник заказа@OrderHandlerType

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {
 int value() default 0;
}

2. Абстрактный бизнес-процессор

Абстрагируйте конкретный бизнес-процессор

public abstract class AbstractHandler {
 abstract public void handle(OrderBO orderBO);
}

3. Сканирование запуска проектаhandlerВход

@Component
@SuppressWarnings({"unused","rawtypes"})
public class HandlerProcessor implements BeanFactoryPostProcessor {
 
 private String basePackage = "com.ecej.order.pipeline.processor";
 
    public static final Logger log = LoggerFactory.getLogger(HandlerProcessor.class);
 
 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  
  Map<Integer,Class> map = new HashMap<Integer,Class>();
  
  ClassScaner.scan(basePackage, OrderHandlerType.class).forEach(x ->{
   int type = x.getAnnotation(OrderHandlerType.class).value();
   map.put(type,x);
  });
  
  beanFactory.registerSingleton(OrderHandlerType.class.getName(), map);
  
  log.info("处理器初始化{}", JSONObject.toJSONString(beanFactory.getBean(OrderHandlerType.class.getName())));
 }
}

4. Инструменты, необходимые для сканирования


public class ClassScaner {
 private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

 private final List<TypeFilter> includeFilters = new ArrayList<TypeFilter>();

 private final List<TypeFilter> excludeFilters = new ArrayList<TypeFilter>();

 private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
 
 //添加包含的Fiter
 public void addIncludeFilter(TypeFilter includeFilter) {
  this.includeFilters.add(includeFilter);
 }

 //添加排除的Fiter
 public void addExcludeFilter(TypeFilter excludeFilter) {
  this.excludeFilters.add(excludeFilter);
 }
 
 //扫描指定的包,获取包下所有的Class
 public static Set<Class<?>> scan(String basePackage,
   Class<?>... targetTypes) {
  ClassScaner cs = new ClassScaner();
  for (Class<?> targetType : targetTypes){
   if(TypeUtils.isAssignable(Annotation.class, targetType)){
    cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
   }else{
    cs.addIncludeFilter(new AssignableTypeFilter(targetType));
   }
  }
  return cs.doScan(basePackage);
 }
 
 //扫描指定的包,获取包下所有的Class
 public static Set<Class<?>> scan(String[] basePackages,
   Class<?>... targetTypes) {
  ClassScaner cs = new ClassScaner();
  for (Class<?> targetType : targetTypes){
   if(TypeUtils.isAssignable(Annotation.class, targetType)){
    cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
   }else{
    cs.addIncludeFilter(new AssignableTypeFilter(targetType));
   }
  }
  Set<Class<?>> classes = new HashSet<Class<?>>();
  for (String s : basePackages){
   classes.addAll(cs.doScan(s));
  }
  return classes;
 }
 
 //扫描指定的包,获取包下所有的Class
 public Set<Class<?>> doScan(String [] basePackages) {
  Set<Class<?>> classes = new HashSet<Class<?>>();
  for (String basePackage :basePackages) {
   classes.addAll(doScan(basePackage));
  }
  return classes;
 }
 
 //扫描指定的包,获取包下所有的Class
 public Set<Class<?>> doScan(String basePackage) {
  Set<Class<?>> classes = new HashSet<Class<?>>();
  try {
   String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
     + ClassUtils.convertClassNameToResourcePath(
       SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";
   Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
   for (int i = 0; i < resources.length; i++) {
    Resource resource = resources[i];
    if (resource.isReadable()) {
     MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
     if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {
      try {
       classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
      } catch (ClassNotFoundException ignore) {}
     }
    }
   }
  } catch (IOException ex) {
   throw new RuntimeException("I/O failure during classpath scanning", ex);
  }
  return classes;
 }
 
 private boolean matches(MetadataReader metadataReader) throws IOException {
  for (TypeFilter tf : this.excludeFilters) {
   if (tf.match(metadataReader, this.metadataReaderFactory)) {
    return false;
   }
  }
  for (TypeFilter tf : this.includeFilters) {
   if (tf.match(metadataReader, this.metadataReaderFactory)) {
    return true;
   }
  }
  return false;
 }
}

5. Создайте экземпляр абстрактного класса в соответствии с типом


@Component
public class HandlerContext {

 @Autowired
 private ApplicationContext beanFactory;

 public  AbstractHandler getInstance(Integer type){
  
  Map<Integer,Class> map = (Map<Integer, Class>) beanFactory.getBean(OrderHandlerType.class.getName());
  
  return (AbstractHandler)beanFactory.getBean(map.get(type));
 }
 
}

6. Вызов записи

Здесь я обрабатываю несколько источников заказов при получении сообщений MQ, и разные источники заказов направляются в разные классы обработки бизнеса.


@Component
@RabbitListener(queues = "OrderPipelineQueue")
public class PipelineSubscribe{
 
 private final Logger LOGGER = LoggerFactory.getLogger(PipelineSubscribe.class);
 
 @Autowired
 private HandlerContext HandlerContext;
 
 @Autowired
 private OrderValidateService orderValidateService;
 
    @RabbitHandler
    public void subscribeMessage(MessageBean bean){
     
     OrderBO orderBO = JSONObject.parseObject(bean.getOrderBO(), OrderBO.class);
     
     if(null != orderBO &&CollectionUtils.isNotEmpty(bean.getType()))
     {
      for(int value:bean.getType())
      {
          AbstractHandler handler = HandlerContext.getInstance(value);
          handler.handle(orderBO);
      }
  }
 }
}

принимающая организацияMessageBeanкод класса

public class MessageBean implements Serializable {
    private static final long serialVersionUID = 5454831432308782668L;
    private String cachKey;
    private List<Integer> type;
    private String orderBO;

    public MessageBean(List<Integer> type, String orderBO) {
        this.type = type;
        this.orderBO = orderBO;
    }
}

Приведенные выше шаблоны проектирования выглядят немного сложными, и некоторые мелкие партнеры задались вопросом: «Выif-else, это так хлопотно, и это пользовательская аннотация, разве не хлопотно создавать столько классов? «Есть еще некоторые друзья, которые запутались в проблемах с производительностью, и производительность режима стратегии может быть не так хороша, какif-else.

Но я думаю, стоит добавить немного сложности, немного пожертвовать производительностью и изменить чистоту и ремонтопригодность кода. Однако у одного человека есть одна идея, как выбрать, зависит от конкретного бизнес-сценария!

Преимущества и недостатки паттерна стратегии

преимущество

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

недостаток

  • Режим стратегии, когда слишком много алгоритмов стратегии, это вызовет много классов стратегии
  • Клиент не знает, какие существуют классы политик, и не может решить, какой класс политик использовать.Это можно решить, инкапсулировав общий общедоступный пакет или рассмотрев возможность использованияIOC容器и依赖注入способ решить.

Ниже приведена часть класса стратегии источника ордера.Надо сказать, что классов стратегий действительно много.在这里插入图片描述

Суммировать

У всего есть две стороны,if-elseНесколько уровней вложенности также имеют свои преимущества и недостатки:

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

  • 策略模式Отделите логику каждой сцены для обслуживания.Один и тот же абстрактный класс имеет несколько подклассов, которые необходимо использовать.if-elseилиswitch-caseПри выборе конкретного подкласса рекомендуется выбрать режим стратегии, его недостатком является то, что он будет генерировать больше файлов классов стратегий.

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

болтовня

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

Изучать шаблоны проектирования не бесполезно, но иногда для этого нет подходящего сценария.Как и бизнес-сценарии, о которых мы говорили сегодня, шаблоны проектирования можно использовать для их идеального решения.

Очень часто приходится изучать много технологий, но не использовать их. При использовании технологии в стабильном проекте будет много соображений. Увеличит ли новая технология сложность системы? Какие у него есть узкие места в производительности? Это необходимо учитывать, ведь стабильность проекта — это самое главное, и никто не осмеливается легко рисковать.

И мы изучаем технологии не только для того, чтобы увидеть, будут ли они использованы в текущем проекте, но и для того, чтобы накапливать технологии и строить долгосрочные планы.Люди идут в высокие места без каких-либо способностей.


Оригинальность не так проста, жгучие волосы на выходе, я надеюсь, что вы можете немного выиграть!

Разобраны и розданы друзьям сотни различных технических электронных книг. Закрыть ответ официального аккаунта【"666"] Самовывоз. Мы создали группу технического обмена с некоторыми друзьями, чтобы обсуждать технологии и делиться технической информацией, стремясь учиться и развиваться вместе.Если вам интересно, отсканируйте код, чтобы присоединиться к нам!