Почему вы всегда используете плохие шаблоны проектирования?

Java задняя часть Шаблоны проектирования

Это 6-й день моего участия в Gengwen Challenge, смотрите подробности мероприятия:Обновить вызов

Ставь лайк и потом смотри, вырабатывай полезную привычку

предисловие

Я часто вижу какие-то статьи о паттернах проектирования, пишу много контента и привожу несколько очень «ярких» примеров. ​

Но у него может быть та же проблема, что и у "Head First Design Patterns": после прочтения я буду, но, похоже, я не смогу его использовать? Или жесткий шаблон проектирования. ​

Вот несколько экстремальных примеров, которые я видел:

  1. Два поля, но и Builder
  2. 3 если, укажите шаблон стратегии
  3. 5 строк кода по-прежнему очень просто инициализировать, но и получить Factory
  4. ...

Что касается того, почему возникает эта проблема... позвольте мне рассказать о моих взглядах

причина

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

Однако для бизнес-кода во многих случаях применять шаблоны проектирования непросто или невозможно хорошо применять шаблоны проектирования. ​

Наиболее часто встречающийсярежим стратегиистатья, зачем? ​

Я предполагаю, что это потому, что это лучше всего написано, и его относительно просто применить в бизнес-коде; в любом немного сложном сценарии вы можете применить шаблон стратегии, чтобы разделить несколько «если» на несколько классов. ​

Это правда, что правильное использование шаблонов стратегии в бизнес-коде может уменьшить сложность, но даже если он используется, он должен использоваться до определенной степени. взорвется... ​

Я видел проект раньше.Хотя это внутренняя система xx, брат R&D может быть одержим. Я нарисовал более 80 классов стратегий, и эти 80 классов разбиты на более чем дюжину групп, по семь или восемь классов стратегий в каждой группе, но код в каждом классе состоит всего из дюжины или двадцати строк, и есть также Есть повторяющийся код. ​

Я спросил его тогда: ты воспитываешь Гу? ​

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

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

Но шаблоны проектирования существуют для решения проблем и уменьшения/передачи сложности, а не для ее увеличения.

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

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

взять каштан

В системе обычно требуется traceId/requestId для объединения всей ссылки в сочетании с печатью журнала или централизованным извлечением APM. ​

Возьмем, к примеру, одно приложение. MDC фреймворка ведения журнала обычно используется для привязки этого traceId. В Filter или некоторых АОП дайте MDC traceID, тогда вся цепочка вызовов сможет использовать этот ID, и при печати логов разные запросы можно будет различать по traceId, вот так:

2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 请求第0步
2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 请求第1步
2021-06-10 18:31:44.227 [ThreadName] [000] INFO loggerName - 请求第2步

2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 请求第0步
2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 请求第1步
2021-06-10 18:31:44.227 [ThreadName] [111] INFO loggerName - 请求第2步

...

Запрос можно отличить по идентификатору трассировки 000/111. ​

Но MDC хранит данные через ThreadLocal, который в конце концов привязан к потокам. Что делать, если в ссылке используется обработка пула потоков? Когда подпоток в пуле потоков печатает журнал, MDC не может получить traceId основного потока, но для этого запроса основной подпоток является ссылкой... ​

Помните эту фразу?

Любую проблему в информатике можно решить, добавив непрямой средний слой.

Здесь с помощью режима делегирования добавляется промежуточный слой, и проблема решается очень хорошо. ​

Поскольку это проблема передачи данных основного подпотока, вам нужно только извлечь traceId в MDC из основного потока при создании подпотока и передать его во вновь созданный подпоток, например :

public class MDCDelegateRunnable implements Runnable{

    private Runnable target;

    private String traceId;

    public MDCDelegateRunnable(Runnable target, String traceId) {
        this.target = target;
        this.traceId = traceId;
    }

    @Override
    public void run() {
        MDC.put("traceId",traceId);
        target.run();
        MDC.remove("traceId");
    }
}

Затем есть еще один пул потоков режима делегата, который будетexecuteпереопределение метода. Оберните исходный объект Runnable в пул потоков, как только чтоMDCDelegateRunnable, при создании передать traceId через параметр построения

public class MDCDelegateExecutorService extends AbstractExecutorService {

    public MDCDelegateExecutorService(AbstractExecutorService target) {
        this.target = target;
    }

    private AbstractExecutorService target;

    @Override
    public void shutdown() {
        target.shutdown();
    }

    //...

    @Override
    public void execute(@NotNull Runnable command) {
        target.execute(new MDCDelegateRunnable(command, MDC.get("traceId")));
    }

}

Готово, давайте тестировать:

public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
    MDC.put("traceId","111");
    new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("runnable: "+MDC.get("traceId"));
        }
    });
    Future<String> future = new MDCDelegateExecutorService((AbstractExecutorService) Executors.newFixedThreadPool(5)).submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return MDC.get("traceId");
        }
    });

    System.out.println("callable: "+future.get());

    System.in.read();
}

//output
runnable: 111
callable: 111

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

Шаблон делегирования в JDK

все еще помнюExecutors#newSingleThreadExecutorМетод создания этого однопоточного пула потоков, то при каких обстоятельствах вам нужен однопоточный пул потоков? ​

Например, мне просто нужна асинхронная операция и получить отдачу.Если я запускаю новый поток напрямую, то возвращаемое значение получать неудобно.Удобно, если я использую Callable/Runnable+Future пула потоков:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future<String> future = executorService.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        // do sth...
        return data;
    }
});

String data = future.get();

executorService.shutdown();

Для однопоточных асинхронных сценариев вам даже не нужно поддерживать пул одноэлементных потоков, и вы можете каждый раз выполнять new/shutdown. Но у меня есть один поток.Неужели немного неудобно каждый раз выключать?Если я где-то забуду выключиться, это не закончится... ​

Разработчики JDK тоже подумали об этой проблеме и решили ее. Подобно приведенному выше примеру, использование простого шаблона делегирования может прекрасно решить эту проблему:

public static ExecutorService newSingleThreadExecutor() {
    //创建 FinalizableDelegatedExecutorService 委托类
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

// 委托类里,在 finalize 被委托的线程池对象的 shutdown方法,自动关闭线程池
static class FinalizableDelegatedExecutorService
    extends DelegatedExecutorService {
    FinalizableDelegatedExecutorService(ExecutorService executor) {
        super(executor);
    }
    protected void finalize() {
        super.shutdown();
    }
}

// 公共的抽象委托线程池……
static class DelegatedExecutorService extends AbstractExecutorService {
    private final ExecutorService e;
    
    DelegatedExecutorService(ExecutorService executor) { e = executor; }
    
    public void execute(Runnable command) { e.execute(command); }
    
    public void shutdown() { e.shutdown(); }
    //...
}

Таким образом, при использованииnewSingleThreadExecutor, вам даже не нужно больше показывать выключение... ​

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

Суммировать

Объединив два приведенных выше примера, как только вы выйдете за рамки бизнес-кода, станет ли шаблон проектирования приложения очень простым? Вам даже не нужно придерживаться шаблонов проектирования.Когда вы столкнетесь с проблемой, вы, естественно, подумаете об использовании шаблонов проектирования для решения проблем, а не об использовании шаблонов проектирования для размножения кода... ​

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

Повторять:Шаблоны проектирования используются для решения проблем и снижения/передачи сложности, а не ее увеличения.

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

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