Чтобы код не выглядел как связка *, я неоднократно использовал это в своей работе

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

Шаблон проектирования цепочки ответственности

определение

Запрос обрабатывается в цепочке, и получатель в цепочке решает, продолжить ли пересылку или прервать текущий поток обработки после обработки.

Применимая сцена

Подходит для многоузловой обработки процессов, каждый узел выполняет свою часть ответственности, и узлы не знают о существовании друг друга, например, поток утверждения OA, механизма фильтрации в веб-разработке java. Приведу пример из жизни: когда я снимал дом, я встретил так называемого черного посредника. Когда я снимал дом, я чувствовал себя богом, но когда я просил его исправить что-то сломанное, он был как внук. Посредник попросил меня найти магазин по обслуживанию клиентов. Служба по работе с клиентами попросила меня снова найти домовладельца, и домовладелец попросил меня найти ее мужа. В конце концов, дело было улажено (вы должны найти постоянного агента по аренде дома).

Опыт

В настоящее время я занимаюсь совокупной оплатой групповых обедов в кампусе. Бизнес-процесс очень прост: 1. Студент открывает код мобильного платежа, чтобы заплатить, 2. Тетя в кафетерии использует машину для сканирования кода платежа, чтобы получить платеж. История университетской столовой такова. Столовая субсидируется, а посуда относительно дешевая. Поэтому школа не желает позволять публике ходить в школьную столовую, чтобы потреблять еду. В связи с этим мы добавили набор логики, чтобы проверить, разрешена ли оплата перед оплатой. следующее: 

  1. Определенная кабинка позволяет потреблять только определенные типы пользователей.Например, кабинка учителя позволяет учителям только потреблять, а кабинка ученика не позволяет потреблять внешним пользователям;

  2. Определенный киоск позволяет определенным типам пользователей потреблять только несколько раз в день, например, кафетерий учителя позволяет ученикам потреблять только один раз в день;

  3. Разрешено ли нехаляльным студентам потреблять, например, в некоторых халяльных ресторанах, нехаляльным студентам не разрешается потреблять;

Для таких ситуаций я установил три типа фильтров, а именно:

SpecificCardUserConsumeLimitFilter: определяет, разрешено ли потребление в соответствии с типом пользователя. 

DayConsumeTimesConsumeLimitFilter: определяет, разрешено ли потребление в соответствии с количеством ежедневного потребления. 

MuslimConsumeLimitFilter: разрешено ли пользователям, не являющимся халяльными, потреблять 

Логика суждения состоит в том, чтобы сначала оценить, может ли текущий пользователь потреблять в этом киоске с помощью SpecificCardUserConsumeLimitFilter. Если разрешено продолжить, DayConsumeTimesConsumeLimitFilter определит, было ли израсходовано количество потребляемых за день блюд, если оно не израсходовано, продолжит использовать MuslimConsumeLimitFilter, чтобы определить, удовлетворяет ли текущий пользователь условиям обеда в ресторане. Три приговора, если одно не выполняется, возвращаются раньше.

Часть кода выглядит следующим образом:

public boolean canConsume(String uid,String shopId,String supplierId){
    //获取用户信息,用户信息包含类型(student:学生,teacher:老师,unknown:未知用户)、名族(han:汉族,mg:蒙古族)
    UserInfo userInfo = getUserInfo(uid);
    //获取消费限制信息,限制信息包含是否允许非清真消费、每种类型的用户是否允许消费以及允许消费的次数
   ConsumeConfigInfo consumeConfigInfo = getConsumeConfigInfo(shopId,supplierId) 

    // 构造消费限制过滤器链条
    ConsumeLimitFilterChain filterChain = new ConsumeLimitFilterChain();
    filterChain.addFilter(new SpecificCardUserConsumeLimitFilter());
    filterChain.addFilter(new DayConsumeTimesConsumeLimitFilter());
    filterChain.addFilter(new MuslimConsumeLimitFilter());
    boolean checkResult = filterChain.doFilter(filterChain, schoolMemberInfo, consumeConfigInfo);

    //filterChain.doFilter方法
   public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
    ConsumeConfigInfo consumeConfigInfo ){
  //迭代调用过滤器
  if(index<filters.size()){
      return filters.get(index++).doFilter(filterChain, userInfo, consumeConfigInfo);
  }
    }

    //SpecificCardUserConsumeLimitFilter.doFilter方法
     public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
    ConsumeConfigInfo consumeConfigInfo ){
                //获取某一类型的消费限制,比如student允许消费,unknown不允许消费
  CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);

  // 判断当前卡用户是否允许消费
  if (consumeCardConfig != null) {
   if ((!CAN_PAY.equals(cardConsumeConfig .getEnabledPay()))) {
       return false;
   }
  }

                //其余情况,继续往后传递
            return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
        }

    //DayConsumeTimesConsumeLimitFilter.doFilter方法
     public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
    ConsumeConfigInfo consumeConfigInfo ){
                //获取某一类型的消费限制,比如student可以消费2次
  CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);

                //获取当前用户今天的消费次数
                int consumeCnt = getConsumeCnt(userInfo)  
  if(consumeCnt >= cardConsumeConfig.getDayConsumeTimesLimit()){
                    return false;
                }

                //其余情况,继续往后传递
                return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
        }

подводить итоги

Логика оценки каждого условия ограничения инкапсулирована в конкретный фильтр. Если логика определенного условия ограничения будет изменена, другие условия не будут затронуты. Если вам нужно добавить новое условие ограничения, вам нужно только воссоздать фильтр и вплести его в FilterChain.

Шаблон разработки стратегии

определение

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

Применимая сцена

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

Опыт

У автора есть требование до того, что после того, как пользователь сканирует QR-код и производит оплату, он отправляет платежное сообщение на кассовое устройство в киоске. Кассир будет транслировать сообщение после получения сообщения. Логика очень проста, просто вызовите платформу push, чтобы отправить сообщение на устройство, но По историческим причинам платформа push для некоторых устройств различается. Устройства класса A предпочитают использовать функцию pigeon push. В случае сбоя необходимо перейти на механизм длительного опроса. Устройства класса B могут напрямую использовать платформу push собственной разработки. Текущая ситуация такова, что форматы сообщений Типа A и Типа B различны (разработаны разными командами и интегрированы позже). В связи с этим я абстрагировал интерфейс PushStrategy. Конкретными реализациями являются IotPushStrategy и XingePushStrategy, которые соответствуют самостоятельно разработанным Для стратегии push платформы push и стратегии push платформы голубя пользователи могут использовать разные стратегии push для разных типов устройств. Часть кода выглядит следующим образом:

/**
 * 推送策略
 * /
public interface PushStrategy {
 /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */
 public CallResult push(AppDeviceVO deviceVO, Object content);
}

IotPushStrategy implements PushStrategy{
        /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */
 public CallResult push(AppDeviceVO deviceVO, Object content){
            //创建自研推送平台需要的推送报文
            Message message = createPushMsg(deviceVO,content);

            //调用推送平台推送接口
            IotMessageService.pushMsg(message);
        }
}

XingePushStrategy implements PushStrategy{
        /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */
 public CallResult push(AppDeviceVO deviceVO, Object content){
            //创建信鸽平台需要的推送报文
            JSONObject jsonObject = createPushMsg(content);

            //调用推送平台推送接口
            if(!XinggePush.pushMsg(message)){
                //降级到长轮询
                ...
            }
        }
}

/**
消息推送Service
*/
MessagePushService{
    pushMsg(AppDeviceVO deviceVO, Object content){
        if(A设备){
            XingePushStrategy.push(deviceVO,content);
        } else if(B设备){
            IotPushStrategy.push(deviceVO,content);
        }
    }
}

подводить итоги

Логика push каждого канала инкапсулирована в определенную стратегию. Изменение определенной стратегии не повлияет на другие стратегии. Поскольку реализован общий интерфейс, стратегии могут быть заменены друг на друга, что удобно для пользователя, например, стратегия отклонения задачи в Java ThreadPoolExecutor. , Когда пул потоков насыщен, стратегия отклонения будет выполняться, и конкретная логика отклонения инкапсулируется в rejectedExecution объекта RejectedExecutionHandler.

Шаблон дизайна шаблона

определение

Ценность шаблона заключается в определении скелета.Процесс обработки проблемы внутри скелета был определен.Общая логика обработки обычно реализуется родительским классом, а персонализированная логика обработки реализуется подклассом. Например, жареные картофельные крошки и жареный тофу мапо, общая логика: 1. нарезать овощи, 2. положить масло, 3. обжарить, 4. приготовить, 1, 2, 4 похожи, но третий шаг отличается. Жареные картофельные крошки нужно обжаривать лопатой, но жареный тофу Мапо нужно подталкивать ложкой, иначе тофу будет тухлым.

сцены, которые будут использоваться

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

Опыт

Следуя примеру предыдущей голосовой трансляции, мы добавили два новых требования позже: 1. При отправке сообщения необходимо добавить трассировку 2. При отправке некоторых каналов произошел сбой, и их необходимо повторить, поэтому текущий процесс выглядит следующим образом: 1. Запускается трассировка 2. Канал Начать push 3. Разрешить ли повторную попытку, если выполнение логики повторной попытки разрешено 4. Трассировка заканчивается там, где 1 и 4 являются универсальными, а 2 и 3 персонализированы, с учетом этого я добавил родительский класс перед конкретной стратегией push Стратегия помещения общей логики в родительский класс модифицированного кода выглядит следующим образом:

abstract class AbstractPushStrategy implements PushStrategy{
    @Override
    public CallResult push(AppDeviceVO deviceVO, Object content) {
        //1.构造span
        Span span = buildSpan();
        //2.具体通道推送逻辑由子类实现
        CallResult callResult = doPush(deviceVO, content);

        //3.是否允许重试逻辑由子类实现,如果允许执行重试逻辑
        if(!callResult.isSuccess() && canRetry()){
            doPush(deviceVO, content);
        }

        //4.trace结束
        span.finish() 
    }

    //具体推送逻辑由子类实现
    protected abstract CallResult doPush(AppDeviceVO deviceDO, Object content) ;

    //是否允许重试由子类实现,有些通道之前没有做消息排重,所有不能重试
    protected abstract boolean canRetry(CallResult callResult);

}

XingePushStrategy extends AbstractPushStrategy{
    @Override
    protected CallResult doPush(AppDeviceVO deviceDO, Object content) {
        //执行推送逻辑
    }

    @Override
    protected boolean canRetry(CallResult callResult){
        return false
    }
}

подводить итоги

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

Шаблон проектирования наблюдателя

Определение режима

Как следует из названия, для этого режима требуются две роли: Observer и Observable. Когда статус Observable изменяется, он уведомляет Observer. Observer обычно реализует общий интерфейс, такой как java.util.Observer, который требуется Observable. При уведомлении Observer, просто вызовите метод обновления Observer один за другим.Успех или неудача обработки Observer не должны влиять на процесс Observable.

сцены, которые будут использоваться

Изменение статуса объекта (Observable) необходимо для уведомления других объектов.Существование Observer не влияет на результат обработки Observable.Добавление и удаление Observer не знает Observable, например подписку на сообщения Kafka, производитель отправляет сообщение в тему, в зависимости от того, 1 или 10 Потребители подписываются на эту тему, производителям не нужно обращать внимание.

Опыт

В модели проектирования цепочки ответственности я решил проблему проверки ограничений потребления с помощью трех фильтров. Один из фильтров используется для проверки количества потреблений. Здесь я просто считываю время потребления пользователя, так как же завершается накопление времени потребления? Как насчет? Фактически, для накопления используется режим наблюдателя. В частности, когда система транзакций получает обратный вызов успешного платежа, она опубликует «событие успешной оплаты» через механизм событий Spring, который отвечает за накопление количества потребления и подписки на голосовую трансляцию. Человек получит «событие успешного платежа», а затем выполнит свою собственную бизнес-логику, нарисует простую диаграмму для описания:

Чтобы код не выглядел как связка *, я неоднократно использовал это в своей работе

Структура кода примерно следующая:

/**
支付回调处理者
*/
PayCallBackController implements ApplicationContextAware {
     private ApplicationContext applicationContext;

    //如果想获取applicationContext需要实现ApplicationContextAware接口,Spring容器会回调setApplicationContext方法将applicationContext注入进来
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }
     @RequestMapping(value = "/pay/callback.do")
     public View callback(HttpServletRequest request){
        if(paySuccess(request){
            //构造支付成功事件
            PaySuccessEvent event = buildPaySuccessEvent(...);
            //通过applicationContext发布事件,从而达到通知观察者的目的
            this.applicationContext.publishEvent(event);
        } 
    }
}
/**
 * 语音播报处理者
 *
 */
public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{
    @Override
    public void onApplicationEvent(PaySuccessEvent event) {
        //语音播报逻辑
    }
}

//其他处理者的逻辑类似

подводить итоги

Режим наблюдателя разделяет наблюдателя и наблюдателя, и присутствие или отсутствие наблюдателя не влияет на существующую логику наблюдателя.

Шаблон проектирования декоратора

определение

Декоратор используется для обертывания исходного класса и улучшения функции, оставаясь при этом прозрачным для пользователя.Например, BufferedInputStream в java может улучшить оболочку InputStream, чтобы обеспечить функцию буферизации.

сцены, которые будут использоваться

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

Опыт

Автор ранее продвигал всю компанию для доступа к системе трассировки, поэтому я также предоставил некоторые инструменты для решения автоматического плетения трассировки и автоматической передачи контекста. Если вам интересно, вы можете прочитать другой мой блог jaeger, используя предварительное исследование, для поддержки межпоточного Для передачи контекста я добавил декоративный класс TraceRunnableWrapper, чтобы прозрачно передать контекст родительского потока дочернему потоку, который полностью прозрачен для пользователя. Код выглядит следующим образом:

/**
可以自动携带trace上下文的Runnable装饰器
*/
public class TraceRunnableWrapper implements Runnable{
    //被包装的目标对象
    private Runnable task;
    private Span parentSpan = null;

    public TraceRunnableWrapper(Runnable task) {
        //1.获取当前线程的上下文(因为new的时候还没有发生线程切换,所以需要在这里将上下文获取)
        //对这块代码感兴趣的可以查看opentracing API
        io.opentracing.Scope currentScope = GlobalTracer.get().scopeManager().active();
        //2.保存父上下文
        parentSpan = currentScope.span();
        this.task = task;
    }

    @Override
    public void run() {
        //run的时候将父线程的上下文绑定到当前线程
        io.opentracing.Scope scope = GlobalTracer.get().scopeManager().activate(parentSpan,false);
        task.run();
    }
}

//使用者
new Thread(new Runnable(){run(...)}).start()替换为new TraceRunnableWrapper(new Runnable(){run(...)}).start()

подводить итоги

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

Режим оформления внешнего вида

определение

Внешний вид должен обеспечить унифицированный вход во внешний мир. Первый - скрыть детали системы, а другой - уменьшить сложность пользователей. Например, DispaterServlet в SpringMvc, все контроллеры доступны через DispaterServlet.

сцены, которые будут использоваться

Упростите работу пользователей и упростите клиентский доступ.

Опыт

Компания автора предоставляет сторонним независимым поставщикам ПО некоторые открытые возможности, такие как управление и контроль оборудования, унифицированные платежи и возможности загрузки выписок. Поскольку они принадлежат разным командам, внешние интерфейсы предоставляются в разных формах. Если их больше, ISV также могут принять его. Однако, когда появилось больше интерфейсов, ISV начали жаловаться на высокую стоимость доступа. Чтобы решить эту проблему, мы добавили интерфейсный контроллер GatewayController перед открытым интерфейсом, который фактически является прототипом нашей более поздней открытой платформы. , GatewayController единообразно предоставляет интерфейс gateway.do внешнему миру. Параметры запроса и параметры ответа внешнего интерфейса унифицированы в GatewayController для конвергенции. GatewayController также использует унифицированный интерфейс при маршрутизации к внутренней службе. Сравнение до и после преобразования выглядит следующим образом:

Чтобы код не выглядел как связка *, я неоднократно использовал это в своей работе

Вероятно, код такой:

使用者:
HttpClient.doPost("/gateway.do","{'method':'trade.create','sign':'wxxaaa','timestamp':'15311111111'},'bizContent':'业务参数'")

GatewayController:
@RequestMapping("/gateway.do")
JSON gateway(HttpServletRequest req){
   //1.组装开放请求
   OpenRequest openRequest = buildOpenRequest(req);

   OpenResponse openResponse = null;
   //2.请求路由
   if("trade.create".equals(openRequest.getMethod()){
       //proxy to trade service by dubbo
       openResponse = TradeFacade.execute(genericParam);
   } else if("iot.message.push".equals(openRequest.getMethod()){
       //proxy to iot service by httpclient
        openResponse = HttpClient.doPost('http://iot.service/generic/execute'genericParam);
   }

   if(openResponse.isSuccess()){
        return {"code":"10000","bizContent":openResponse.getResult()};
   }else{
        return {"code":"20000","bizCode":openResponse.getCode()};
   }

}

подводить итоги

Режим внешнего вида используется для защиты некоторых деталей внутри системы и снижения стоимости доступа пользователя. Возьмем, к примеру, GatewayController. Он объединяет аутентификацию ISV, проверку интерфейса и другие повторяющиеся задачи. Независимым поставщикам программного обеспечения необходимо только подключить разные интерфейсы Что касается набора интерфейсов протоколов, конвергенция осуществляется уровнем GatewayController.

рекомендация

отblog.51cto.com/14570694/2540472