Практическое занятие 6. Использование шаблонов проектирования
Шаблоны проектирования GoF — это многократно используемые решения широко распространенных проблем, возникающих при разработке программного обеспечения. Многие разработчики искали пути повышения гибкости и степени повторного использования своих программ. Найденные решения воплощены в краткой и легко применимой на практике форме.
В общем случае шаблон состоит из четырех основных элементов:
1) имя. Точное имя предоставляет возможность сразу понять проблему и определить решение. Уровень абстракции при проектировании повышается;
2) задача. Область применения в рамках решения конкретной проблемы;
3) решение. Абстрактное описание элементов дизайна задачи проектирования и способа ее решения с помощью обобщенного набора классов;
4) результаты.
Шаблоны классифицируются по разным критериям, наиболее распространенным из которых является назначение шаблона. Вследствие этого выделяются порождающие шаблоны, структурные шаблоны и шаблоны поведения.
Несмотря на все различия, шаблоны взаимосвязаны, и нередко небольшое изменение ответственности или связи того или иного класса может привести к тому, что применение одного шаблона приводит к замене исходного шаблона на другой, часто даже неродственный.
Порождающие шаблоны предназначаются для организации процесса создания объектов и все до единого соответствуют шаблону Creator из GRASP.
К порождающим шаблонам относятся:
Abstract Factory (Абстрактная Фабрика) — предоставляет интерфейс для создания связанных между собой объектов семейств классов без указания их конкретных реализаций (families of product objects);
Factory Method (Фабричный метод) — определяет интерфейс для создания объектов из иерархического семейства классов на основе передаваемых данных (subclass of object that is instantiated);
Builder (Строитель) — создает объект конкретного класса различными способами (how a composite object gets created);
Singleton (Одиночка) — гарантирует существование только одного или конечного числа экземпляров класса (the sole instance of a class);
Prototype (Прототип) — применяется при создании сложных объектов.
На основе прототипа объекты сохраняются и воссоздаются, например, путем копирования (class of object that is instantiated).
Структурные шаблоны GoF отвечают за композицию объектов и классов, и не только за объединение частей приложения, но и за их разделение.
К структурным шаблонам относятся:
Adapter (Адаптер) — применяется при необходимости использовать классы вместе с несвязанными интерфейсами. Поведение адаптируемого класса при этом изменяется на необходимое (interface to an object);
Bridge (Мост) — разделяет представление класса и его реализацию, позволяя независимо изменять то и другое (implementation of an object);
Composite (Компоновщик) — группирует объекты в иерархические древовидные структуры и позволяет работать с единичным объектом так же, как с группой объектов (structure and composition of an object);
Decorator (Декоратор) — представляет способ изменения поведения объекта без создания подклассов. Позволяет использовать поведение одного объекта в другом (responsibilities of an object without subclassing);
Facade (Фасад) — создает класс с общим интерфейсом высокого уровня к некоторому числу интерфейсов в подсистеме (interface to a subsystem);
Flyweight (Легковес) — разделяет свойства класса для оптимальной поддержки большого числа мелких объектов (storage costs of objects);
Proxy (Заместитель) — подменяет сложный объект более простым и осуществляет контроль доступа к нему (how an object is accessed... its location).
Шаблоны поведения GoF характеризуют способы взаимодействия классов или объектов между собой.
К шаблонам поведения относятся:
Chain of Responsibility (Цепочка Обязанностей) — организует независимую от объекта-отправителя цепочку не знающих возможностей друг друга объектов-получателей, которые передают запрос друг другу (object that can fulfill a request);
Command (Команда) — используется для определения по некоторому признаку объекта конкретного класса, которому будет передан запрос для обработки (when and how a request is fulfilled);
Iterator (Итератор) — позволяет последовательно обойти все элементы коллекции или другого составного объекта, не зная деталей внутреннего представления данных (how an aggregate’s elements are accessed, traversed);
Mediator (Посредник) — позволяет снизить число связей между классами при большом их количестве, выделяя один класс, знающий все о методах других классов (how and which objects interact with each other);
Memento (Хранитель) — сохраняет текущее состояние объекта для дальнейшего восстановления (what private information is stored outside an object, and when);
Observer (Наблюдатель) — позволяет при зависимости между объектами типа «один ко многим» отслеживать изменения объекта (number of objects that depend on another object; how the dependent objects stay up to date);
State (Состояние) — позволяет объекту изменять свое поведение за счет изменения внутреннего объекта состояния (states of an object);
Strategy (Стратегия) — задает набор алгоритмов с возможностью выбора одного из классов для выполнения конкретной задачи во время создания объекта (an algorithm);
Template Method (Шаблонный Метод) — создает родительский класс, использующий несколько методов, реализация которых возложена на производные классы (steps of an algorithm);
Visitor (Посетитель) — представляет операцию в одном или нескольких связанных классах некоторой структуры, которую вызывает специфичный для каждого такого класса метод в другом классе (operations that can be applied to object(s) without changing their class(es));
Interpreter (Интерпретатор) — для определенного способа представления информации определяет правила (grammar and interpretation of a language).
Задание. Разработать простое приложение Java с использованием шаблонов Factory, Builder и Chain of Responsibility.
Шаблон Factory
С помощью данного шаблона реализуем логику, которая выводит сообщения debug или error на консоль или записывает в файл. Интерфейс ILogger описывает два основных метода debug с параметром msg и error с параметром msg, где msg - сообщение, которое нужно отобразить на консоли или записать в файл. Созданы два класса, имплементирующие интерфейс ILogger, такие как ConsoleLoggerImpl – для вывода сообщения на консоль и FileLoggerImpl – для записи сообщения в файл. Так же создан класс Factory, который с помощью метода getLogger возвращает FileLoggerImpl или ConsoleLoggerImpl на основании свойства FileLogging = true (или false), находящемся в файле logger.properties. Если значение true, то getLogger вернет FileLoggerImpl, иначе ConsoleLoggerImpl. В классе FactoryMethodPatternDemo приведен пример использования шаблона Factory на практике. Ниже см. текст программы.
ILogger.java
package by.bsac.patterns.factory;
public interface ILogger {
// Write out a debug message
public void debug(String msg);
// Write out an error message
public void error(String msg);
}
ConsoleLoggerImpl.java
package by.bsac.patterns.factory;
public class ConsoleLoggerImpl implements ILogger {
ConsoleLoggerImpl() {} // Not accessible from other packages !
public void debug(String msg) {
System.out.println("DEBUG: " + msg);
}
public void error( String msg) {
System.err.println("ERROR: " + msg);
}
}
FileLoggerImpl.java
package by.bsac.patterns.factory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileLoggerImpl implements ILogger {
public void debug(String msg) {
try {
File file = new File("DebugLog.log");
// create file if doesn't exist
if (!file.exists()) {
file.createNewFile();
}
// true = append file
FileWriter fileWritter = new FileWriter(file.getName(), true);
BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
bufferWritter.write("DEBUG:" + msg);
bufferWritter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void error(String msg) {
try {
File file = new File("ErrorLog.log");
// create file if doesn't exist
if (!file.exists()) {
file.createNewFile();
}
// true = append file
FileWriter fileWritter = new FileWriter(file.getName(), true);
BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
bufferWritter.write("ERROR:" + msg);
bufferWritter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Factory.java
package by.bsac.patterns.factory;
import java.io.IOException;
import java.util.Properties;
public class Factory {
public ILogger getLogger() {
if (isFileLoggingEnabled()) {
return new FileLoggerImpl();
} else {
return new ConsoleLoggerImpl();
}
}
// helper method, check if FileLogging is ON
// if so, log message to a file else print it to console.
private boolean isFileLoggingEnabled() {
Properties p = new Properties();
try {
p.load(getClass().getResourceAsStream("logger.properties"));
String fileLoggingValue = p.getProperty("FileLogging");
if ("true".equalsIgnoreCase(fileLoggingValue))
return true;
else
return false;
} catch (IOException e) {
return false;
}
}
}