Главная страница
Навигация по странице:

  • Поперечные области ответственности

  • АОП-инфраструктуры на «чистом» Java

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница22 из 49
    1   ...   18   19   20   21   22   23   24   25   ...   49
    186
    Глава 11 . Системы
    Истинное внедрение зависимостей идет еще на один шаг вперед . Класс не пред- принимает непосредственных действий по разрешению своих зависимостей; он остается абсолютно пассивным . Вместо этого он предоставляет set
    -методы и/или аргументы конструктора, используемые для внедрения зависимостей .
    В процессе конструирования контейнер DI создает экземпляры необходимых объектов (обычно по требованию) и использует аргументы конструктора или set
    -методы для скрепления зависимостей . Фактически используемые зависи- мые объекты задаются в конфигурационном файле или на программном уровне в специализированном конструирующем модуле .
    Самый известный DI-контейнер для Java присутствует в Spring Framework
    1
    Подключаемые объекты перечисляются в конфигурационном файле XML, по- сле чего конкретный объект запрашивается по имени в коде Java . Пример будет рассмотрен ниже .
    Но как же преимущества ОТЛОЖЕННОЙ ИНИЦИАЛИЗАЦИИ? Эта идиома иногда бывает полезной и при внедрении зависимостей . Во-первых, большинство
    DI-контейнеров не конструирует объекты до того момента, когда это станет не- обходимо . Во-вторых, многие из этих контейнеров предоставляют механизмы использования фабрик или конструирования посредников (proxies), которые могут использоваться для ОТЛОЖЕННОЙ ИНИЦИАЛИЗАЦИИ и других аналогичных оптимизаций
    2
    Масштабирование
    Города вырастают из городков, которые, в свою очередь, появляются на месте деревень . Дороги сначала узки и едва заметны, но со временем они расширя- ются и покрываются камнем . Мелкие строения и пустые места заполняются более крупными зданиями, часть из которых в конечном итоге будет заменена небоскребами .
    На первых порах в городе полностью отсутствует инфраструктура: водопровод, электричество, канализация и (о ужас!) Интернет . Все эти возможности добав- ляются позднее, с ростом населения и плотности застройки .
    Рост не обходится без проблем . Сколько раз вам приходилось едва ползти в по- токе машин вдоль проекта по «расширению дороги», когда вы спрашивали себя:
    «Почему нельзя было сразу построить дорогу достаточной ширины?!»
    Но иначе и быть не могло . Кто сможет объяснить затраты на строительство ше- стиполосной магистрали в середине маленького городка, которому предрекают расширение? Да и кто бы захотел иметь такую дорогу в своем городе?
    1
    См . [Spring] и описание Spring .NET .
    2
    Не забывайте, что отложенная инициализация — всего лишь разновидность оптимизации… и возможно, преждевременная!
    186

    Масштабирование
    187
    Возможность построить «правильную систему с первого раза» — миф . Вместо этого мы сегодня реализуем текущие потребности, а завтра перерабатываем и расширяем систему для реализации новых потребностей . В этом заключается суть итеративной, пошаговой гибкой разработки . Разработка через тестирование, рефакторинг и полученный в результате их применения чистый код обеспечива- ют работу этой схемы на уровне кода .
    А как же системный уровень? Разве архитектура системы не требует предвари- тельного планирования? Не может же она последовательно расти от простого к сложному?
    В этом проявляется важнейшее отличие программных систем от физических. Ар- хитектура программных систем может развиваться последовательно, если обе- спечить правильное разделение ответственности.
    Как вы вскоре убедитесь, нематериальная природа программных систем делает это возможным . Но давайте начнем с контрпримера архитектуры, в которой нормальное разделение ответственности отсутствует .
    Исходные архитектуры EJB1 и EJB2 не обеспечивали должного разделения об- ластей ответственности и поэтому создавали лишние барьеры для естественного роста . Возьмем хотя бы компонент-сущность (Entity Bean) для постоянного (per- sistent) класса . Компонентом-сущностью называется представление реляционных данных (иначе говоря, записи таблицы) в памяти .
    Для начала необходимо определить локальный (внутрипроцессный) или удален- ный (на отдельной JVM) интерфейс, который будет использоваться клиентами .
    Возможный локальный интерфейс представлен в листинге 11 .1 .
    листинг 11 .1 . Локальный интерфейс EJB2 для EJB Bank package com.example.banking;
    import java.util.Collections;
    import javax.ejb.*;
    public interface BankLocal extends java.ejb.EJBLocalObject {
    String getStreetAddr1() throws EJBException;
    String getStreetAddr2() throws EJBException;
    String getCity() throws EJBException;
    String getState() throws EJBException;
    String getZipCode() throws EJBException;
    void setStreetAddr1(String street1) throws EJBException;
    void setStreetAddr2(String street2) throws EJBException;
    void setCity(String city) throws EJBException;
    void setState(String state) throws EJBException;
    void setZipCode(String zip) throws EJBException;
    Collection getAccounts() throws EJBException;
    void setAccounts(Collection accounts) throws EJBException;
    void addAccount(AccountDTO accountDTO) throws EJBException;
    }
    187

    188
    Глава 11 . Системы
    В интерфейс включены некоторые атрибуты адреса
    Bank
    , а также коллекция счетов, принадлежащих банку; данные каждого счета представляются отдель- ным EJB
    Account
    . В листинге 11 .2 приведен соответствующий класс реализации компонента
    Bank
    листинг 11 .2 . Соответствующая реализация компонента-сущности EJB2
    package com.example.banking;
    import java.util.Collections;
    import javax.ejb.*;
    public abstract class Bank implements javax.ejb.EntityBean {
    // Бизнес-логика...
    public abstract String getStreetAddr1();
    public abstract String getStreetAddr2();
    public abstract String getCity();
    public abstract String getState();
    public abstract String getZipCode();
    public abstract void setStreetAddr1(String street1);
    public abstract void setStreetAddr2(String street2);
    public abstract void setCity(String city);
    public abstract void setState(String state);
    public abstract void setZipCode(String zip);
    public abstract Collection getAccounts();
    public abstract void setAccounts(Collection accounts);
    public void addAccount(AccountDTO accountDTO) {
    InitialContext context = new InitialContext();
    AccountHomeLocal accountHome = context.lookup("AccountHomeLocal");
    AccountLocal account = accountHome.create(accountDTO);
    Collection accounts = getAccounts();
    accounts.add(account);
    }
    // Логика контейнера EJB
    public abstract void setId(Integer id);
    public abstract Integer getId();
    public Integer ejbCreate(Integer id) { ... }
    public void ejbPostCreate(Integer id) { ... }
    // Остальные методы должны быть реализованы, но обычно остаются пустыми:
    public void setEntityContext(EntityContext ctx) {} public void unsetEntityContext() {}
    public void ejbActivate() {}
    public void ejbPassivate() {}
    public void ejbLoad() {}
    public void ejbStore() {}
    public void ejbRemove() {}
    }
    В листинге не приведен ни соответствующий интерфейс
    LocalHome
    (по сути — фабрика, используемая для создания объектов), ни один из возможных методов поиска
    Bank
    , которые вы можете добавить .
    Наконец, вы должны написать один или несколько дескрипторов в форма- те XML, которые определяют подробности соответствия между объектом
    188

    Масштабирование
    189
    и реляционными данными, желаемое транзакционное поведение, ограничения безопасности и т . д .
    Бизнес-логика тесно привязана к «контейнеру» приложения EJB2 . Вы должны субклассировать контейнерные типы, а также предоставить многие методы жиз- ненного цикла, необходимые для контейнера .
    Привязка к тяжеловесному контейнеру затрудняет изолированное модульное тестирование . Приходится либо имитировать контейнер, что непросто, либо тратить много времени на развертывание EJB и тестов на реальном сервере . По- вторное использование за пределами архитектуры EJB2 практически невозможно из-за жесткой привязки .
    Наконец, такое решение противоречит принципам объектно-ориентированного программирования . Один компонент не может наследовать от другого компонен- та . Обратите внимание на логику добавления нового счета . В EJB2 компоненты часто определяют «объекты передачи данных» (DTO), которые фактически пред- ставляют собой «структуры без поведения» . Обычно это приводит к появлению избыточных типов, содержащих по сути одинаковые данные, и необходимости использования стереотипного кода для копирования данных между объектами .
    Поперечные области ответственности
    В некоторых областях архитектура EJB2 приближается к полноценному разде- лению ответственности . Например, желательное поведение в области транзак- ционности, безопасности и сохранения объектов объявляется в дескрипторах независимо от исходного кода .
    Такие области, как сохранение объектов, выходят за рамки естественных границ объектов предметной области . Например, все объекты обычно сохраняются по одной стратегии, с использованием определенной СУБД
    1
    вместо неструктуриро- ванных файлов, с определенной схемой выбора имен таблиц и столбцов, единой транзакционной семантикой и т . д .
    Теоретически возможен модульный, инкапсулированный подход к определению стратегии сохранения объектов . Однако на практике вам приходится повторять по сути одинаковый код, реализующий стратегию сохранения, во многих объ- ектах . Для подобных областей используется термин «поперечные области от-
    ветственности» . При этом инфраструктура сохранения может быть модульной, и логика предметной области, рассматриваемая в изоляции, тоже может быть мо- дульной . Проблемы возникают в точках пересечения этих областей . Можно ска- зать, что подход, использованный в архитектуре EJB по отношению к сохранению объектов, безопасности и транзакциям, предвосхитил аспектно-ориентированное
    программирование (АОП
    2
    ), которое представляет собой универсальный подход к восстановлению модульности для поперечных областей ответственности .
    1
    Система управления базами данных .
    2
    За общей информацией об аспектах обращайтесь к [AOSD], а за конкретной информацией об AspectJ — к [AspectJ] и [Colyer] .
    189

    190
    Глава 11 . Системы
    В АОП специальные модульные конструкции, называемые аспектами, опреде- ляют, в каких точках системы поведение должно меняться некоторым после- довательным образом в соответствии с потребностями определенной области ответственности . Определение осуществляется на уровне декларативного или программного механизма .
    В примере с сохранением объектов вы объявляете, какие объекты, атрибуты и т . д . должны сохраняться, а затем делегируете задачи сохранения своей инфраструк- туре сохранения . Изменения в поведении вносятся инфраструктурой АОП без вмешательства в целевой код
    1
    . Рассмотрим три аспекта (или «аспекто-подобных» механизма) в Java .
    Посредники
    Посредники (proxies) хорошо подходят для простых ситуаций — например, для создания «оберток» для вызова методов отдельных объектов или классов . Тем не менее динамические посредники, содержащиеся в JDK, работают только с интерфейсами . Чтобы создать посредника для класса, приходится использовать библиотеки для выполнения манипуляций с байт-кодом — такие, как CGLIB,
    ASM или Javassist
    2
    В листинге 11 .3 приведена заготовка посредника JDK, обеспечивающего под- держку сохранения объектов в нашем приложении
    Bank
    (представлены только методы чтения/записи списка счетов) .
    листинг 11 .3 . Пример посредника JDK
    // Bank.java (подавление имен пакетов...)
    import java.utils.*;
    // Абстрактное представление банка.
    public interface Bank {
    Collection getAccounts();
    void setAccounts(Collection accounts);
    }
    // BankImpl.java import java.utils.*;
    // POJO-объект ("Plain Old Java Object"), реализующий абстракцию.
    public class BankImpl implements Bank {
    private List accounts;
    public Collection getAccounts() { return accounts;
    }
    public void setAccounts(Collection accounts) {
    1
    То есть без необходимости ручного редактирования целевого кода .
    2
    См . [CGLIB], [ASM] и [Javassist] .
    190

    Посредники
    191
    this.accounts = new ArrayList(); for (Account account: accounts) {
    this.accounts.add(account);
    }
    }
    }
    // BankProxyHandler.java import java.lang.reflect.*;
    import java.util.*;
    // Реализация InvocationHandler, необходимая для API посредника.
    public class BankProxyHandler implements InvocationHandler {
    private Bank bank;
    public BankHandler (Bank bank) {
    this.bank = bank;
    }
    // Метод, определенный в InvocationHandler public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (methodName.equals("getAccounts")) {
    bank.setAccounts(getAccountsFromDatabase());
    return bank.getAccounts();
    } else if (methodName.equals("setAccounts")) {
    bank.setAccounts((Collection) args[0]);
    setAccountsToDatabase(bank.getAccounts());
    return null;
    } else {
    }
    }
    // Подробности:
    protected Collection getAccountsFromDatabase() { ... }
    protected void setAccountsToDatabase(Collection accounts) { ... }
    }
    // В другом месте...
    Bank bank = (Bank) Proxy.newProxyInstance(
    Bank.class.getClassLoader(), new Class[] { Bank.class },
    new BankProxyHandler(new BankImpl()));
    Мы определили интерфейс
    Bank
    , который будет инкапсулироваться посредником, и POJO-объект («Plain Old Java Object», то есть «обычный Java-объект»)
    Ban- kImpl
    , реализующий бизнес-логику . (Вскоре мы вернемся к теме POJO-объектов) .
    Для работы посредника необходим объект
    InvocationHandler
    , который вызывается для реализации всех вызовов методов
    Bank
    , обращенных к посреднику . Наша реа-
    191

    192
    Глава 11 . Системы лизация
    BankProxyHandler использует механизм рефлексии Java для отображения вызовов обобщенных методов на соответствующие методы
    BankImpl
    Код получается весьма объемистым и относительно сложным, даже в этом про- стом случае
    1
    . Не меньше проблем создает и использование библиотек для ма- нипуляций с байт-кодом . Объем и сложность кода — два основных недостатка посредников . Эти два фактора усложняют создание чистого кода! Кроме того, у посредников не существует механизма определения «точек интереса» обще- системного уровня, необходимых для полноценного АОП-решения
    2
    АОП-инфраструктуры на «чистом» Java
    К счастью, большая часть шаблонного кода посредников может автоматически обрабатываться вспомогательными средствами . Посредники используются во внутренней реализации нескольких инфраструктур Java — например, Spring
    AOP и JBoss AOP — для реализации аспектов непосредственно на уровне Java
    3
    В Spring бизнес-логика записывается в форме POJO-объектов . Такие объекты полностью сосредоточены на своей предметной области . Они не имеют зави- симостей во внешних инфраструктурах (или любых других областях); соответ- ственно им присуща большая концептуальная простота и удобство тестирования .
    Благодаря относительной простоте вам будет проще обеспечить правильную реализацию соответствующих пожеланий пользователей, а также сопровождение и эволюцию кода при появлении новых пожеланий .
    Вся необходимая инфраструктура приложения, включая поперечные области ответственности (сохранение объектов, транзакции, безопасность, кэширование, преодоление отказов и т . д .), определяется при помощи декларативных конфи- гурационных файлов или API . Во многих случаях вы фактически определяете аспекты библиотек Spring или JBoss, а инфраструктура берет на себя всю ме- ханику использования посредников Java или библиотек байт-кода в режиме, прозрачном для пользователя . Объявления управляют контейнером внедрения зависимостей (DI), который создает экземпляры основных объектов и связывает их по мере необходимости .
    В листинге 11 .4 приведен типичный фрагмент конфигурационного файла Spring
    V2 .5 app.xml
    4 1
    Более подробные примеры API посредников и его использования можно найти, например, в [Goetz] .
    2
    Методологию АОП иногда путают с приемами, используемыми для ее реализации напри- мер перехватом методов и «инкапсуляцией» посредников . Подлинная ценность АОП- системы заключается в способности модульного, компактного определения системного поведения .
    3
    См . [Spring] и [JBoss] . «Непосредственно на уровне Java» в данном случае означает «без применения AspectJ» .
    4
    По материалам http://www .theserverside .com/tt/articles/article .tss?l=IntrotoSpring25 .
    192

    АОП-инфраструктуры на «чистом» Java
    193
    листинг 11 .4 . Конфигурационный файл Spring 2.X

    destroy-method="close"
    p:driverClassName="com.mysql.jdbc.Driver"
    p:url="jdbc:mysql://localhost:3306/mydb"
    p:username="me"/>
    p:dataSource-ref="appDataSource"/>
    p:dataAccessObject-ref="bankDataAccessObject"/>

    Каждый компонент напоминает одну из частей русской «матрешки»: объект предметной области
    Bank
    «упаковывается» в объект доступа к данным DAO (Data
    Accessor Object), который, в свою очередь, упаковывается в объект источника данных JDBC (рис . 11 .3) .
    Рис . 11 .3 . «Матрешка» из декораторов
    Клиент полагает, что он вызывает метод getAccounts()
    объекта
    Bank
    , но в дейст- вительности он взаимодействует с внешним объектом из набора вложен- ных ДЕКОРАТОРОВ [GOF], расширяющих базовое поведение POJO-объекта
    Bank
    . Мы могли бы добавить другие декораторы для транзакций, кэширования и т . д .
    Чтобы запросить у DI-контейнера объекты верхнего уровня, заданные в файле
    XML, достаточно включить в приложение несколько строк:
    XmlBeanFactory bf =
    new XmlBeanFactory(new ClassPathResource("app.xml", getClass()));
    Bank bank = (Bank) bf.getBean("bank");
    Так как объем кода, специфического для Spring, минимален, приложение почти полностью изолировано от Spring . Тем самым устраняются все проблемы жесткой привязки, характерные для таких систем, как EJB2 .
    193

    1   ...   18   19   20   21   22   23   24   25   ...   49


    написать администратору сайта