Если вы читали мой предыдущий блог об использовании явной блокировки , вы, возможно, помните, что я написал некоторый пример кода, который переводил деньги между двумя случайными банковскими счетами, используя следующий алгоритм:
IF fromAccount is locked THEN IF toAccount is locked THEN withDraw money from the fromAccount deposit money into the toAccount END IF END IF
Вы также можете помнить, что это был многопоточный код, изначально написанный для создания тупика. Чтобы продемонстрировать
интерфейс блокировки явной блокировки и реализацию ReentrantLock, мне нужно было добавить механизм блокировки потоков в мой простой POJO аккаунта, и поэтому возник вопрос: как мне это сделать?
public class Account { private final int number; private int balance; public Account(int number, int openingBalance) { this.number = number; this.balance = openingBalance; } public void withDrawAmount(int amount) throws OverdrawnException { if (amount > balance) { throw new OverdrawnException(); } balance -= amount; } public void deposit(int amount) { balance += amount; } public int getNumber() { return number; } public int getBalance() { return balance; } }
Когда вы знаете, что ваш код будет читать большое количество программистов, вы сосредоточитесь на том, чтобы попытаться найти наилучшее возможное решение, которое продемонстрирует, о чем вы говорите.
Много лет назад у меня был руководитель группы, который готовился к квалификации менеджера, и у него были следующие вопросы, постоянно написанные на доске позади него:
- В чем проблема?
- Каковы решения?
- Какое самое лучшее решение?
Поэтому, если проблема заключается в том, как добавить блокировку потоков в POJO, то есть несколько возможных решений:
Решение 1 — Использование наследования
Идея здесь заключается в том, чтобы сделать аккаунт наследуемым от ReentrantLock.
Что вы можете сказать об этой идее? Во-первых, он будет работать, поскольку он предоставляет методы, которые должен использовать пример кода передачи. Однако, если вы посмотрите поближе, вы увидите, что оно нарушает правило ISA, поскольку вы не можете сказать «Account ISA ReentrantLock» — оно просто не работает и демонстрирует полное отсутствие базового понимания ориентации объекта.
Решение 2 Использование агрегации и метода get ()
Вторым возможным решением было бы для POJO учетной записи агрегировать объект ReentrantLock и выставлять его с помощью метода getLock (), но является ли это хорошей идеей? Во-первых, вы можете сказать, что это будет работать, и это демонстрирует проблему из моего предыдущего блога. Это, однако, нарушает инкапсуляцию объекта Account, излишне раскрывая внутреннюю структуру Account всему миру. Кроме того, это также
нарушает законы Деметры . На более практическом уровне, если бы я использовал эту технику в своем производственном коде и по какой-то причине мне пришлось внести изменения в этот код, то мне придется вносить изменения по всей кодовой базе, где бы ни происходил getLock () делая это очень рискованной стратегией.
Решение 3 Учетная запись реализует интерфейс блокировки и делегирует ReentrantLock
В этом решении класс Account реализует интерфейс Lock, а также экземпляр HASA ReentrantLock. Все вызовы интерфейса блокировки класса Account теперь делегируются агрегированному экземпляру ReentrantLock.
Что вы можете сказать об этой идее? Ну, опять же, это будет работать. Внутренние объекты объекта Account полностью скрыты от любого вызывающего, что хорошо, поскольку любые будущие изменения в механизме блокировки, скорее всего, ограничиваются исключительно классом Account и не распространяются на большую часть кодовой базы.
С другой стороны, эта идея требует немного больше кода для реализации, чем предыдущие два, и интерфейс объекта Account больше и поэтому немного более грубый.
Наилучшее возможное решение
Из трех приведенных выше вариантов я выбрал решение номер три по причинам, изложенным выше, и код выглядел следующим образом:
public class Account implements Lock { private final int number; private int balance; private final ReentrantLock lock; public Account(int number, int openingBalance) { this.number = number; this.balance = openingBalance; this.lock = new ReentrantLock(); } public void withDrawAmount(int amount) throws OverdrawnException { if (amount > balance) { throw new OverdrawnException(); } balance -= amount; } public void deposit(int amount) { balance += amount; } public int getNumber() { return number; } public int getBalance() { return balance; } // ------- Lock interface implementation @Override public void lock() { lock.lock(); } @Override public void lockInterruptibly() throws InterruptedException { lock.lockInterruptibly(); } @Override public Condition newCondition() { return lock.newCondition(); } @Override public boolean tryLock() { return lock.tryLock(); } @Override public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException { return lock.tryLock(arg0, arg1); } @Override public void unlock() { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }
Я не знаю о вас, но мне кажется, что многие программисты просто запишут первое, что приходит им в голову, не рассматривая другие варианты, и я думаю, что это, вероятно, результат того, как их учили написать код.
Моя дочь учится на архитектора, и с самого первого дня им приходится вставать и говорить о своих проектах перед другими студентами, преподавателями и настоящими архитекторами. Они должны оправдать свой выбор и поддержать их; то, что, насколько я знаю, не происходит на курсах по информатике
2 .
Не поймите меня неправильно, для разработчиков было бы финансово невозможно встать и оправдать дизайн каждого класса, который они пишут, перед группой коллег и экспертов, но из-за характера программирования наша работа обычно скрыта с простого сайта, поскольку единственные люди, которые могут оценить
искусство программирования, — это другие программисты, что обычно означает, что вы можете (и люди часто это делают) сойти с рук с помощью убийства.
Итак, есть ли новый анти-шаблон для определения здесь? Я предполагаю, что это не относится к
Большому мячу грязи или
Объекту Бога , но
«Первое решение — единственное и лучшее решение» , вероятно, является причиной большого количества проблем.
1 Может быть, я не всегда получаю это написать …
2 Если это так, пожалуйста, дайте мне знать …