Содержание
Для многих разработчиков инверсия управления (IoC) является нечеткой концепцией, которая практически не применяется в реальном мире. В лучшем случае это просто эквивалент внедрения зависимостей (DI). Однако уравнение IoC = DI верно, когда обе стороны ссылаются на инвертирование управления управлением зависимостями. Хотя внедрение зависимостей на самом деле является широко известной формой IoC, правда состоит в том, что IoC представляет собой гораздо более широкую парадигму проектирования программного обеспечения, которая может быть реализована с помощью нескольких шаблонов . В этой статье мы рассмотрим, как внедрение зависимости, шаблон наблюдателя и шаблонный шаблонный метод реализуют инверсию управления.
Как и во многих других шаблонах проектирования из богатого репертуара, реализация IoC является компромиссом для разработчика:
- Разработка компонентов с высокой степенью разделения и инкапсуляция логики приложения в одном месте являются прямыми и естественными последствиями реализации IoC.
- С другой стороны, реализация требует построения как минимум одного уровня косвенности, а в некоторых случаях это может быть просто излишним.
Рассмотрение нескольких конкретных реализаций поможет вам найти компромисс между этими свойствами.
Демистификация парадигмы IoC
Инверсия контроля — это паттерн с несколькими наклонами. Типичный пример IoC дает Мартин Фаулер в следующей простой программе, которая собирает пользовательские данные с консоли:
public static void main(String[] args) { while (true) { BufferedReader userInputReader = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Please enter some text: "); try { System.out.println(userInputReader.readLine()); } catch (IOException e) { e.printStackTrace(); } } }
В этом случае поток управления программой определяется методом main
: в бесконечном цикле он читает пользовательский ввод и выводит его на консоль. Здесь метод полностью контролирует, когда читать пользовательский ввод и когда его печатать.
Теперь рассмотрим обновленную версию программы, которая использует графический интерфейс пользователя (GUI) для сбора входных данных через текстовое поле, кнопку и прослушиватель действий, привязанных к нему. В этом контексте каждый раз, когда пользователь нажимает кнопку, введенный текст собирается слушателем и распечатывается на панели.
В этой версии программы она фактически находится под контролем модели прослушивателя событий (в данном случае это платформа) для вызова кода, написанного разработчиком, для чтения и печати пользовательского ввода. Проще говоря, фреймворк будет вызывать код разработчика, а не наоборот. Фреймворк на самом деле является расширяемой структурой, которая предоставляет разработчику набор особых точек для внедрения сегментов пользовательского кода.
В этом смысле управление было эффективно инвертировано .
С более общей точки зрения каждая вызываемая точка расширения, определяемая платформой, либо в форме реализации (ов) интерфейса , либо наследования реализации (также называемого подклассом), является четко определенной формой IoC.
Рассмотрим случай простого сервлета :
public class MyServlet extends HttpServlet { protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } }
Здесь класс HttpServlet
(принадлежащий платформе) — это элемент, который полностью контролирует программу, а не подкласс MyServlet
. Код в методах doGet()
и doPost()
автоматически вызывается сервлетом в ответ на HTTP-запросы GET и POST после его создания контейнером сервлета.
По сравнению с типичной перспективой наследования, где подклассы имеют элемент управления вместо базового класса, элемент управления был инвертирован .
Фактически, методы сервлета являются реализацией шаблона метода шаблона , который мы подробно обсудим позже.
В случае каркасов, которые придерживаются принципа открытого / закрытого за счет предоставления расширяемого API, роль разработчика, использующего каркас, сводится к определению их собственного набора пользовательских классов, либо путем реализации одного или нескольких интерфейсов, предоставляемых каркасом, либо наследуя от существующих базовых классов. В свою очередь, экземпляры классов непосредственно создаются и вызываются платформой.