Статьи

Куда делись все «новые» операторы?

В моем методе 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;
  }

Когда я впервые предлагаю людям, что логика приложения не должна создавать свои собственные зависимости, я получаю два общих возражения, которые являются мифами:

  1. «Так что теперь каждому классу нужна фабрика, поэтому у меня в два раза больше классов! Небеса нет! Обратите внимание, как наша ServerFactory действовала как фабрика для множества различных классов. Глядя на это, я насчитал около 7 классов, которые мы создали, чтобы подключить наше приложение. Так что это неправда, что у нас однозначное соответствие. В теории вам нужна только одна Фабрика на время жизни объекта. Вам нужна одна фабрика для всех долгоживущих объектов (ваших синглетонов) и одна для всех объектов времени жизни запроса и так далее. Сейчас на практике мы далее разделяем их по связанным понятиям. (Но это обсуждение отдельной статьи в блоге.) Важно понимать, что: да, у вас будет немного больше классов, но это будет совсем близко к удвоению нагрузки.
  2. «Если каждый объект запрашивает свои зависимости, то мне придется передать эти зависимости всем вызывающим. Это очень усложнит добавление новых зависимостей в классы ». Миф здесь заключается в том, что 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;
  }

С http://misko.hevery.com