Это 6-й день моего участия в Gengwen Challenge, смотрите подробности мероприятия:Обновить вызов
Ставь лайк и потом смотри, вырабатывай полезную привычку
предисловие
Я часто вижу какие-то статьи о паттернах проектирования, пишу много контента и привожу несколько очень «ярких» примеров.
Но у него может быть та же проблема, что и у "Head First Design Patterns": после прочтения я буду, но, похоже, я не смогу его использовать? Или жесткий шаблон проектирования.
Вот несколько экстремальных примеров, которые я видел:
- Два поля, но и Builder
- 3 если, укажите шаблон стратегии
- 5 строк кода по-прежнему очень просто инициализировать, но и получить Factory
- ...
Что касается того, почему возникает эта проблема... позвольте мне рассказать о моих взглядах
причина
Большая часть персонала отдела исследований и разработок занимается разработкой бизнес-функций, которую часто называют 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 может быть утечка
Суммировать
Объединив два приведенных выше примера, как только вы выйдете за рамки бизнес-кода, станет ли шаблон проектирования приложения очень простым? Вам даже не нужно придерживаться шаблонов проектирования.Когда вы столкнетесь с проблемой, вы, естественно, подумаете об использовании шаблонов проектирования для решения проблем, а не об использовании шаблонов проектирования для размножения кода...
В чистом бизнес-коде преимущества правильного разделения и поддержания чистоты и удобочитаемости кода гораздо сильнее, чем набор шаблонов проектирования.
Повторять:Шаблоны проектирования используются для решения проблем и снижения/передачи сложности, а не ее увеличения.
Вышеизложенное является только моим личным мнением, если у вас другое мнение, пожалуйста, оставьте сообщение в комментариях.
Нелегко быть оригинальным, и несанкционированная перепечатка запрещена. Если моя статья полезна для вас, пожалуйста, поставьте лайк/добавьте в избранное/подпишитесь, чтобы поддержать и поддержать ее ❤❤❤❤❤❤