Рубрики
Без рубрики

Шаблоны проектирования Java J2EE

Автор оригинала: David Landup.

Обзор

Это четвертая и заключительная статья в короткой серии , посвященной Шаблонам проектирования на Java, и прямое продолжение предыдущей статьи – Поведенческие шаблоны проектирования на Java .

Шаблоны J2EE

Шаблоны J2EE заинтересованы в предоставлении решений, касающихся Java EE. Эти модели широко приняты другими структурами и проектами. Например, например: Весна .

Шаблоны J2EE, которые рассматриваются в этой статье, являются:

  • Шаблон MVC
  • Шаблон Бизнес-делегата
  • Шаблон Составного объекта
  • Шаблон объекта Доступа к Данным
  • Шаблон Переднего Контроллера
  • Шаблон Фильтра Перехвата
  • Шаблон локатора услуг
  • Шаблон объекта Переноса

Шаблон MVC

Это один из самых известных и наиболее часто используемых шаблонов из этой категории. Он вращается вокруг идеи Model-View-Controller , откуда и происходит аббревиатура.

Модели в основном являются объектами, или, точнее, Pojo, используемыми в качестве чертежей/моделей для всех объектов, которые будут использоваться в приложении.

Представления представляют презентационный аспект данных и информации, содержащихся в моделях.

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

Реализация

Тем не менее, давайте начнем с первого из трех компонентов этого шаблона – модели:

public class Employee {
    private int employeeId;
    private String name;

    public int getEmployeeId() {
        return employeeId;
    }
    public void setEmployeeId(int id) {
        this.employeeId = id;
    }
    public String getName() {
        return name;
    }
    public void setEmployeeName(String name) {
        this.name = name;
    }
}

Нам нужен способ представления данных из модели, поэтому мы определяем представление именно для этой цели:

public class EmployeeView {
    public void printEmployeeInformation(String employeeName, int employeeId) {
        System.out.println("Employee information: ");
        System.out.println("ID: " + employeeId);
        System.out.println("Name: " + employeeName);
    }
}

Представление отвечает за форматирование информации удобным для пользователя способом.

Как только это будет устранено, давайте определим контроллер. Этот контроллер будет использовать как модель, так и представление для создания экземпляра модели, заполнения ее некоторыми данными, а затем переместит ее в представление для просмотра клиентом:

public class EmployeeController {
    private Employee employee;
    private EmployeeView employeeView;

    public EmployeeController(Employee employee, EmployeeView employeeView) {
        this.employee = employee;
        this.employeeView = employeeView;
    }

    public String getEmployeeName() {
        return employee.getName();
    }
    public void setEmployeeName(String name) {
        employee.setEmployeeName(name);
    }
    public int getEmployeeId() {
        return employee.getEmployeeId();
    }
    public void setEmployeeId(int id) {
        employee.setEmployeeId(id);
    }
    public void updateView() {
        employeeView.printEmployeeInformation(employee.getName(), employee.getEmployeeId());
    }
}

Завершив все три компонента этого шаблона, мы можем завершить этот пример.

Чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
       Employee employee = getEmployeeFromDatabase();
       EmployeeView view = new EmployeeView();
       EmployeeController controller = new EmployeeController(employee, view);

       controller.updateView();

       controller.setEmployeeId(5);

       controller.updateView();
    }

    // simulating a database
    public static Employee getEmployeeFromDatabase() {
        Employee employee = new Employee();
        employee.setEmployeeName("David");
        employee.setEmployeeId(1);
        return employee;
    }
}

Запуск этого фрагмента кода приведет к:

Employee information:
ID: 1
Name: David
Employee information:
ID: 5
Name: David

Шаблон Бизнес-делегата

Шаблон бизнес-делегата используется для отделения уровня представления от бизнес-уровня, чтобы свести к минимуму количество запросов между клиентом (презентацией) и бизнес-уровнями.

Реализация

Давайте начнем с определения интерфейса для наших бизнес-услуг:

public interface BusinessService {
    public void process();
}

Затем давайте определим два конкретных класса, реализующих этот интерфейс:

public class EJBService implements BusinessService {
    @Override
    public void process() {
        System.out.println("Processing using the EJB Service.");
    }
}

public class JMSService implements BusinessService {
    @Override
    public void process() {
        System.out.println("Processing using the JSM Service.");
    }
}

Давайте определим службу поиска. Объект службы поиска должен обеспечивать соответствующие бизнес-реализации и доступ бизнес-объекта к логике бизнес-делегата:

public class BusinessLookUp {
    public BusinessService getBusinessService(String type) {
        if (type.equalsIgnoreCase("ejb")) {
            return new EJBService();
        } else if (type.equalsIgnoreCase("JMS")) {
            return new JMSService();
        } else {
            return null;
        }
    }
}

Теперь мы можем определить нашего бизнес-делегата:

public class BusinessDelegate {
    private BusinessLookUp lookupService = new BusinessLookUp();
    private BusinessService businessService;
    private String type;

    public void setServiceType(String type) {
        this.type = type;
    }

    public void process() {
        businessService = lookupService.getBusinessService(type);
        businessService.process();
    }
}

Он действует как точка доступа к бизнес-сервисам для Клиента для использования:

public class Client {
    BusinessDelegate businessDelegate;

    public Client(BusinessDelegate businessDelegate) {
        this.businessDelegate = businessDelegate;
    }

    public void process() {
        businessDelegate.process();
    }
}

А теперь, чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
        BusinessDelegate businessDelegate = new BusinessDelegate();
        businessDelegate.setServiceType("EJB");

        Client client = new Client(businessDelegate);
        client.process();

        businessDelegate.setServiceType("JMS");
        client.process();
    }
}

Запуск этого фрагмента кода приведет к:

Processing using the EJB Service.
Processing using the JSM Service.

Шаблон Составного объекта

Шаблон составного объекта представляет собой график объектов, который при обновлении запускает обновление для всех зависимых объектов на графике.

Он в основном используется в Enterprise JavaBeans (EJB) , который не является очень популярным API, поскольку его заменили другие платформы и инструменты, такие как Spring Framework и его многочисленные инструменты.

Реализация

Давайте определим два класса, которые содержат данные, которые необходимо было бы обновить в другом классе:

public class Employee {
    private String name;
    private String jobSuccess;

    public void setJobSuccess(String jobSuccess) {
        this.jobSuccess = jobSuccess;
    }

    public String getJobSuccess() {
        return jobSuccess;
    }
}
public class Manager {
    private String name;
    private String satisfaction;

    public void setSatisfaction(String satisfaction) {
        this.satisfaction = satisfaction;
    }

    public String getSatisfaction() {
        return satisfaction;
    }
}

Если Сотрудник работает хорошо, Менеджер удовлетворен, и наоборот.

Поскольку смысл этого шаблона состоит в том, чтобы не позволять бобам действовать как “мелкозернистым” объектам в одиночку, мы представляем Крупнозернистый объект . Этот объект управляет своими собственными отношениями с другими объектами:

public class CoarseGrainedObject {
    Employee employee = new Employee();
    Manager manager = new Manager();

    public void setData(String jobSuccess, String satisfaction) {
        employee.setJobSuccess(jobSuccess);
        manager.setSatisfaction(satisfaction);
    }

    public String[] getData() {
        return new String[] {"Employee : " + employee.getJobSuccess(),"Manager: " + 
            manager.getSatisfaction()};
    }
}

После этого нам нужно определить Составную сущность класс. Этот класс сам по себе является крупнозернистым объектом и может ссылаться на другой:

public class CompositeEntity {
    private CoarseGrainedObject cgo = new CoarseGrainedObject();

    public void setData(String jobSuccess, String satisfaction) {
        cgo.setData(jobSuccess, satisfaction);
    }

    public String[] getData() {
        return cgo.getData();
    }
}

С учетом этого нам просто нужно Клиент для использования Составной сущности :

public class Client {
    private CompositeEntity compositeEntity = new CompositeEntity();

    public void print() {
        for (int i = 0; i < compositeEntity.getData().length; i++) {
            System.out.println(compositeEntity.getData()[i]);
        }
    }

    public void setData(String jobSuccess, String satisfaction) {
        compositeEntity.setData(jobSuccess, satisfaction);
    }
}

И чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        client.setData("Successful", "Satisfied");
        client.print();
        client.setData("Failed", "Unsatisfied");
        client.print();
    }
}

Запуск этого фрагмента кода приведет к:

Employee : Successful
Manager: Satisfied
Employee : Failed
Manager: Unsatisfied

Шаблон объекта Доступа к Данным

Шаблон объекта доступа к данным, чаще всего сокращенный до DAO, представляет собой шаблон, в котором объекты предназначены для связи со слоем данных.

Эти объекты часто создают экземпляры “Фабрик сеансов” для этой цели и обрабатывают всю логику взаимодействия с базой данных.

Стандартная практика заключается в создании интерфейса DAO, за которым следует конкретный класс, реализующий интерфейс и все методы, определенные в нем.

Реализация

Следуя стандартной практике, давайте определим наш интерфейс DAO:

public interface EmployeeDAO {
    public List getAllEmployees();
    public Employee getEmployeeById(int id);
    public void addEmployee(Employee e);
    public void updateEmployee(Employee e);
    public void deleteEmployee(Employee e);
}

И наш конкретный класс реализации вместе с ним:

public class EmployeeDAOImpl implements EmployeeDAO {
    List employeeList;

    public EmployeeDAOImpl() {
        employeeList = new ArrayList();
        Employee david = new Employee(5, "David");
        Employee scott = new Employee(7, "Scott");
        Employee jessica = new Employee(12, "Jessica");
        Employee rebecca = new Employee(16, "Rebecca");
        employeeList.add(david);
        employeeList.add(scott);
        employeeList.add(jessica);
        employeeList.add(rebecca);
    }

    @Override
    public List getAllEmployees() {
        return employeeList;
    }
    @Override
    public Employee getEmployeeById(int id) {
        return employeeList.get(id);
    }
    @Override
    public void addEmployee(Employee e) {
        employeeList.add(e);
        System.out.println("Successfully added " + e.getName());
    }
    @Override
    public void updateEmployee(Employee e) {
        employeeList.get(e.getEmployeeId()).setEmployeeName(e.getName());
        System.out.println("Successfully update name of employee with id: " + e.getEmployeeId());
    }
    @Override
    public void deleteEmployee(Employee e) {
        employeeList.remove(e.getEmployeeId());
        System.out.println("Successfully removed employee: " + e.getName() + "with the ID: " + e.getEmployeeId());
    }
}

Мы будем использовать эти два класса для добавления, извлечения, обновления или удаления пользователей из нашей базы данных:

public class Employee {
    private int employeeId;
    private String name;

    public Employee(int id, String name) {
        this.employeeId = id;
        this.name = name;
    }

    public int getEmployeeId() {
        return employeeId;
    }
    public void setEmployeeId(int id) {
        this.employeeId = id;
    }
    public String getName() {
        return name;
    }
    public void setEmployeeName(String name) {
        this.name = name;
    }
}

И чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
        EmployeeDAO employeeDao = new EmployeeDAOImpl();

        for(Employee employee : employeeDao.getAllEmployees()) {
            System.out.println("Employee info: |Name: " + employee.getName() + ", ID: " + employee.getEmployeeId() + "|");
        }
    }
}

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Запуск этого фрагмента кода приведет к:

Employee info: |Name: David, ID: 5|
Employee info: |Name: Scott, ID: 7|
Employee info: |Name: Jessica, ID: 12|
Employee info: |Name: Rebecca, ID: 16|

Шаблон Переднего Контроллера

При отправке запроса Передний контроллер является первым контроллером, к которому он обращается. На основе запроса он решает, какой контроллер является наиболее подходящим для его обработки, после чего передает запрос выбранному контроллеру.

Фронтальный контроллер чаще всего используется в Веб-приложениях в виде ДиспетчерсЕрвлета .

Реализация

Для этой реализации мы определим два простых представления: FrontController и Диспетчер :

public class MainView {
    public void showView() {
        System.out.println("Showing main view.");
    }
}

public class EmployeeView {
    public void showView() {
        System.out.println("Showing Employee view.");
    }
}

Запрос на любой из этих двух может появиться в любой момент. Мы используем Диспетчер для обработки запроса, указывая на правильное представление, после того, как FrontController обработал запрос изначально:

public class Dispatcher {
    private MainView mainView;
    private EmployeeView employeeView;

    public Dispatcher() {
        mainView = new MainView();
        employeeView = new EmployeeView();
    }

    public void dispatch(String request) {
        if(request.equalsIgnoreCase("EMPLOYEE")) {
            employeeView.showView();
        } else {
            mainView.showView();
        }
    }
}
public class FrontController {
    private Dispatcher dispatcher;

    public FrontController() {
        dispatcher = new Dispatcher();
    }

    private boolean isAuthenticUser() {
        System.out.println("User has successfully authenticated.");
        return true;
    }

    private void trackRequest(String request) {
        System.out.println("Request: " + request);
    }

    public void dispatchRequest(String request) {
        trackRequest(request);

        if(isAuthenticUser()) {
            dispatcher.dispatch(request);
        }
    }
}

И чтобы проиллюстрировать суть шаблона:

public class Main {
    public static void main(String[] args) {
        FrontController frontController = new FrontController();
        frontController.dispatchRequest("MAIN");
        frontController.dispatchRequest("EMPLOYEE");
    }
}

Запуск этого фрагмента кода приведет к:

Request: MAIN
User has successfully authenticated.
Showing main view.
Request: EMPLOYEE
User has successfully authenticated.
Showing Employee view.

Шаблон Фильтра Перехвата

Фильтры используются еще до того, как запрос передается соответствующим контроллерам для обработки. Эти фильтры могут существовать в виде цепочки Фильтров и включать несколько фильтров или просто существовать как один фильтр.

Тем не менее, они проверяют авторизацию, аутентификацию, поддерживаемые браузеры, нарушает ли путь запроса какие-либо ограничения и ограничения и т.д.

Реализация

Мы создадим простую цепочку фильтров с парой фильтров для перехвата запроса после достижения цели.

Давайте начнем с определения интерфейса для самого Фильтра :

public interface Filter {
    public void execute(String request);
}

И пара конкретных реализаций:

public class AuthenticationFilter implements Filter {
    @Override
    public void execute(String request) {
        System.out.println("Authentication request: " + request);
    }
}

public class DebuggingFilter implements Filter {
    @Override
    public void execute(String request) {
        System.out.println("Logging request: " + request);
    }
}

И, наконец, Цель запроса:

public class Target {
    public void execute(String request) {
        System.out.println("Executing request: " + request);
    }
}

Определив цепочку Фильтров , мы можем добавить несколько фильтров для перехвата запроса. Давайте определим один для наших двух фильтров:

public class FilterChain {
    private List filters = new ArrayList<>();
    private Target target;

    public void addFilter(Filter filter) {
        filters.add(filter);
    }

    public void execute(String request) {
        for (Filter filter : filters) {
            filter.execute(request);
        }
        target.execute(request);
    }

    public void setTarget(Target target) {
        this.target = target;
    }
}

Теперь нам нужен класс менеджера, чтобы помочь управлять этой цепочкой фильтров :

public class FilterManager {
    FilterChain filterChain;

    public FilterManager(Target target) {
        filterChain = new FilterChain();
        filterChain.setTarget(target);
    }

    public void addFilter(Filter filter) {
        filterChain.addFilter(filter);
    }

    public void filterRequest(String request) {
        filterChain.execute(request);
    }
}

И, наконец, Клиент будет использовать Менеджер фильтров для отправки запроса приложению:

public class Client {
    FilterManager filterManager;

    public void setFilterManager(FilterManager filterManager) {
        this.filterManager = filterManager;
    }

    public void sendRequest(String request) {
        filterManager.filterRequest(request);
    }
}

Теперь, чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
        FilterManager filterManager = new FilterManager(new Target());
        filterManager.addFilter(new AuthenticationFilter());
        filterManager.addFilter(new DebuggingFilter());

        Client client = new Client();
        client.setFilterManager(filterManager);
        client.sendRequest("Index");
    }
}

Запуск этого фрагмента кода приведет к:

Authentication request: Index
Logging request: Index
Executing request: Index

Запрос был пропущен через оба фильтра из цепочки Фильтров , прежде чем быть перенаправленным на Цель .

Шаблон локатора услуг

Шаблон , часто встречающийся в Веб-приложениях , шаблон локатора служб используется для разделения Потребителей услуг и конкретных классов, таких как реализации DAO.

Шаблон ищет подходящую службу, сохраняет ее в хранилище кэша, чтобы уменьшить количество запросов и, следовательно, нагрузку на сервер, и предоставляет приложению их экземпляры.

Реализация

Давайте начнем эту реализацию с определения общего сервиса интерфейса:

public interface Service {
    public String getServiceName();
    public void execute();
}

Несколько конкретных классов реализуют этот интерфейс:

public class EmployeeService implements Service {
    @Override
    public String getServiceName() {
        return "Employee Service";
    }

    @Override
    public void execute() {
        System.out.println("Executing Employee Service...");
    }
}

public class CustomerService implements Service {
    @Override
    public String getServiceName() {
        return "Customer Service";
    }

    @Override
    public void execute() {
        System.out.println("Executing Customer Service...");
    }
}

Согласно шаблону, при поиске этих сервисов мы должны кэшировать их, чтобы уменьшить нагрузку на сервер:

public class Cache {
    private List services;

    public Cache() {
        services = new ArrayList();
    }

    public Service getService(String serviceName) {
        for(Service service : services) {
            if(service.getServiceName().equalsIgnoreCase(serviceName)) {
                System.out.println("Returning cached " + serviceName);
                return service;
            }
        }
        return null;
    }

    public void addService(Service newService) {
        boolean exists = false;

        for(Service service : services){
            if(service.getServiceName().equalsIgnoreCase(newService.getServiceName())) {
                exists = true;
            }
        }
        if(!exists) {
            services.add(newService);
        }
    }
}

Нам также нужен класс для поиска и создания экземпляров наших сервисов:

public class InitialContext {
    public Object lookup(String jndiName) {
        if(jndiName.equalsIgnoreCase("EmployeeService")) {
            System.out.println("Looking up and initializing Employee Service...");
            return new EmployeeService();
        } else if(jndiName.equalsIgnoreCase("CustomerService")) {
            System.out.println("Looking up and initializing Customer Service...");
            return new CustomerService();
        }
        return null;
    }
}

И, наконец, мы можем определить класс Locator для предоставления клиенту, который использует класс InitialContext для поиска служб и класс Cache для их кэширования для дальнейшего использования.

public class Locator {
    private static Cache cache;

    static {
        cache = new Cache();
    }

    public static Service getService(String jndiName) {
        Service service = cache.getService(jndiName);

        if(service != null) {
            return service;
        }

        InitialContext context = new InitialContext();
        Service service1 = (Service)context.lookup(jndiName);
        cache.addService(service1);
        return service1;
    }
}

И чтобы проиллюстрировать суть этой модели:

public class Main {
    public static void main(String[] args) {
        Service service = Locator.getService("EmployeeService");
        service.execute();
        service = Locator.getService("CustomerService");
        service.execute();
    }
}

Запуск этого фрагмента кода приведет к:

Looking up and initializing Employee Service...
Executing Employee Service...
Looking up and initializing Customer Service...
Executing Customer Service...

Шаблон объекта Переноса

Этот шаблон используется для передачи объектов с большим количеством полей и параметров за один раз. Шаблон объекта передачи использует новые объекты, используемые только для целей передачи, обычно передаваемые в DAO.

Эти объекты являются сериализуемыми POJOs . У них есть поля, соответствующие им геттеры и сеттеры, и никакой другой логики.

Реализация

Объект может выглядеть следующим образом:

public class EmployeeVO {
    private int employeeId;
    private String name;

    public EmployeeVO(int employeeId, String name) {
        this.employeeId = employeeId;
        this.name = name;
    }

    public int getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(int id) {
        this.employeeId = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Пожалуйста, обратите внимание, что объект содержит только несколько полей для краткости.

Пример нового объекта, который используется только для целей переноса:

public class EmployeeBO {
    List employees;

    public EmployeeBO() {
        employees = new ArrayList<>();
        EmployeeVO david = new EmployeeVO(1, "David");
        EmployeeVO scott = new EmployeeVO(2, "Scott");
        EmployeeVO jessica = new EmployeeVO(3, "Jessica");
        employees.add(david);
        employees.add(scott);
        employees.add(jessica);
    }

    public void deleteEmployee(EmployeeVO employee) {
        employees.remove(employee.getEmployeeId());
        System.out.println("Employee with ID: " + employee.getEmployeeId() + " was successfully deleted.");
    }

    public List getAllEmployees() {
        return employees;
    }

    public EmployeeVO getEmployee(int id) {
        return employees.get(id);
    }

    public void updateEmployee(EmployeeVO employee) {
        employees.get(employee.getEmployeeId()).setName(employee.getName());
        System.out.println("Employee with ID: " + employee.getEmployeeId() + " successfully updated.");
    }
}

И чтобы проиллюстрировать суть шаблона:

public class Main {
    public static void main(String[] args) {
        EmployeeBO employeeBo = new EmployeeBO();

        for(EmployeeVO employee : employeeBo.getAllEmployees()) {
            System.out.println("Employee: |" + employee.getName() + ", ID: " + employee.getEmployeeId() + "|");
        }

        EmployeeVO employee = employeeBo.getAllEmployees().get(0);
        employee.setName("Andrew");
        employeeBo.updateEmployee(employee);

        employee = employeeBo.getEmployee(0);
        System.out.println("Employee: |" + employee.getName() + ", ID: " + employee.getEmployeeId() + "|");

    }
}

Запуск этого фрагмента кода приведет к:

Employee: |David, ID: 1|
Employee: |Scott, ID: 2|
Employee: |Jessica, ID: 3|
Employee with ID: 1 successfully updated.
Employee: |Andrew, ID: 1|

Вывод

При этом все шаблоны проектирования J2EE в Java полностью освещены с рабочими примерами.

На этом мы завершаем нашу короткую серию статей о шаблонах проектирования Java. Если вы нашли это информативным и пропустили что-либо из предыдущих, не стесняйтесь проверить их тоже:

  • Шаблоны творческого проектирования на Java
  • Структурные шаблоны проектирования в Java
  • Поведенческие шаблоны проектирования в Java