В моем методе main () лучше, чем ваш, мы рассмотрели, как должен выглядеть метод main (). Там мы ввели четкое разделение между (1) обязанностью построения графа объектов и (2) ответственностью за запуск приложения. Причина, по которой это разделение важно, была изложена в разделе «Как думать о« новом »операторе . Итак, давайте посмотрим, куда делись все новые операторы …
Прежде чем мы пойдем дальше, я хочу, чтобы вы визуализировали ваше приложение в своем уме. Думайте о компонентах вашего приложения как о физических блоках, которые необходимо соединить для работы. Провода — это ссылки, которые один компонент имеет на другой. В идеальном приложении вы можете изменить поведение приложения, просто подключив компоненты по-разному. Например, вместо создания экземпляра LDAPAuthenticator вы создаете экземпляр KerberosAuthenticator и подключаете KerberosAuthenticator к соответствующим компонентам, которые должны знать об Authenticator. Это основная идея. При удалении новых операторов из логики приложения вы отделили ответственность проводки компонентов от логики приложения, и это является весьма желательным. Итак, теперь возникает проблема: куда делись все новые операторы?
Сначала давайте посмотрим на ручной процесс подключения. В методе main () мы попросили ServerFactory создать нам сервер (в нашем случае — Jetty Web Server). Теперь сервер должен быть подключен вместе с сервлетами. Сервлеты, в свою очередь, должны быть подключены к их сервисам и так далее. Обратите внимание, что на заводском сильфоне полно «новых» операторов. Мы создаем новые компоненты и передаем ссылки одного компонента другому для создания проводки. Это инстанцирование и подключение, которое я просил вас представить выше. (Полный источник ):
public Server buildServer() { Server server = new Server(); SocketConnector socketConnector = new SocketConnector(); socketConnector.setPort(8080); server.addConnector(socketConnector); new ServletBuilder(server) .addServlet("/calc", new CalculatorServlet( new Calculator())) .addServlet("/time", new TimeServlet( new Provider() { public Date get() { return new Date(); } })); return server; }
Когда я впервые предлагаю людям, что логика приложения не должна создавать свои собственные зависимости, я получаю два общих возражения, которые являются мифами:
- «Так что теперь каждому классу нужна фабрика, поэтому у меня в два раза больше классов! Небеса нет! Обратите внимание, как наша ServerFactory действовала как фабрика для множества различных классов. Глядя на это, я насчитал около 7 классов, которые мы создали, чтобы подключить наше приложение. Так что это неправда, что у нас однозначное соответствие. В теории вам нужна только одна Фабрика на время жизни объекта. Вам нужна одна фабрика для всех долгоживущих объектов (ваших синглетонов) и одна для всех объектов времени жизни запроса и так далее. Сейчас на практике мы далее разделяем их по связанным понятиям. (Но это обсуждение отдельной статьи в блоге.) Важно понимать, что: да, у вас будет немного больше классов, но это будет совсем близко к удвоению нагрузки.
- «Если каждый объект запрашивает свои зависимости, то мне придется передать эти зависимости всем вызывающим. Это очень усложнит добавление новых зависимостей в классы ». Миф здесь заключается в том, что call-graph и instantiation-graph — это одно и то же. Мы заглянули в этот миф в « Где ушли все одиночки» . Обратите внимание, что сервер Jetty вызывает TimeServlet, который вызывает Date. Если бы конструктор Date или TimeServlet внезапно нуждался в новом аргументе, он не затронул бы ни одного из вызывающих. Единственный код, который должен был бы измениться, является фабричным классом выше. Это потому, что мы выделили проблему инстанцирования / связывания в этот фабричный класс. Так что на самом деле это позволяет добавлять зависимости не сложнее.
Теперь есть несколько важных вещей для запоминания. На фабриках не должно быть логики! Просто инстанцирование / проводка (так что у вас, вероятно, не будет никаких условностей или циклов). Я должен быть в состоянии вызвать фабрику для создания сервера в модульном тесте без какого-либо доступа к файловой системе, потокам или любым другим дорогостоящим операциям ЦП или операций ввода-вывода. Фабрика создает сервер, но не запускает его. Еще одна вещь, которую вы должны иметь в виду, это то, что процесс подключения часто контролируется аргументами командной строки. Это позволяет вашему приложению вести себя по-разному, в зависимости от того, что вы передаете в командной строке. Разница в поведении заключается не в условных кодах, разбросанных по всей базе кода, а в другом способе подключения вашего приложения.
Наконец, вот несколько мыслей о моей любви / ненависти к одиночкам (упоминается здесь и здесь) Сначала небольшой обзор синглетонов. Одиночка со строчными буквами ‘s’ является хорошим синглтоном и просто означает один экземпляр некоторого класса. Синглтон с заглавной буквой «S» — это шаблон проектирования, представляющий собой синглтон (один экземпляр некоторого класса) с глобальной переменной «экземпляр», которая делает его доступным из любого места. Это глобальная переменная экземпляра, которая делает его глобально доступным, что превращает синглтон в синглтон. Таким образом, синглтон приемлем и иногда очень полезен для дизайна, но Singleton полагается на изменчивое глобальное состояние, которое препятствует тестированию и делает хрупкий, трудный для тестирования дизайн. Теперь обратите внимание, что наша фабрика создала целую кучу синглетонов, как в одном экземпляре чего-то. Также обратите внимание, как эти синглтоны явно передавались в сервисы, которые в них нуждались.Поэтому, если вам нужен синглтон, вы просто создаете один его экземпляр на фабрике, а затем передаете этот экземпляр во все компоненты, которые в них нуждаются. Нет необходимости в глобальной переменной.
Например, обычное использование Singleton для пула соединений с БД. В нашем примере вы просто создадите новый класс DBConnectionPool в самой верхней фабрике (см. Выше), которая отвечает за создание долгоживущих объектов. Теперь предположим, что для CalculatorServlet и TimeServlet потребуется пул соединений. В этом случае мы просто передадим один и тот же экземпляр DBConnectionPool во все места, где он необходим. Обратите внимание, что у нас есть синглтон (DBConnectionPool), но у нас нет глобальных переменных, связанных с этим синглтоном.
public Server buildServer() { Server server = new Server(); SocketConnector socketConnector = new SocketConnector(); socketConnector.setPort(8080); server.addConnector(socketConnector); DBConnectionPool pool = new DBConnectionPool(); new ServletBuilder(server) .addServlet("/calc", new CalculatorServlet( pool, new Calculator())) .addServlet("/time", new TimeServlet( pool, new Provider() { public Date get() { return new Date(); } })); return server; }