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

«Классикой жанра» считается книжка «банды четырех» (англ: «Gang Of Four» или просто GoF) — книга написанная Эрихом Гаммой и соавторами в 1995 году и посвященная шаблонам проектирования.

Из книг по шаблоном именно для Java-программистов читал «Применение шаблонов Java».
Описание шаблонов, которые я здесь привожу построено на следующем принципе.
Название шаблона — ссылка на статью в википдеии. Пример — фрагмент java-кода, где этот шаблон может встречаться в стандартном Java API или в каком-то «бытовом» случае.
Для шаблонов, примеры которых я не смог найти место в стандартной реализации, приводиться просто его краткое описание. Другими словами там, где можно обойтись от использования «оригнальных» и «понятных» примеров ООП — CarFactory, Car, Ford или Fruit, Apple или Person, Student, Prepod и так далее, я старался использовать привычные для программиста StringBuilder, MouseAdapter, Reader. Т.е. классы, объекты которых мы используем каждый день.
В любом случае, непосредственно детального описание шаблонов здесь нет. Описание каждого шаблона проектирования можно прочитать либо в книгах, либо в википедии или нагуглить. По мере возможности в статье будут добавлены ссылки на соответствующие термины в википедии.
Creational/Порождающий
AbstractFactory
// Создает DOM Document Builder в зависимости от используемого парсера DocumentBuilderFactory documentBuilderFactory = ...; documentBuilderFactory.newDocumentBuilder();
// Возвращает разные Toolkit'ы для Windows, Linux, MacOS Toolkit.getDefaultToolkit();
// Сначала добавляем разные значения, потом строим строку
StringBuilder sb = new StringBuilder();
sb.append("Hello ");
sb.append(123);
String txt = sb.toString();
// Есть прототип из которого легко создавать копии объекты Date protypeDate = ...; // Получаем такую же дату просто клонируя Date date = protypeDate.clone();
// Всегда возвращаем один класс Console System.console();
Structural/Структурный
new MouseAdapter() {
// Перекрываем только нужный нам метод и используем объект как MouseListener
public void mouseClicked(MouseEvent e) { System.out.println("mouseClicked"); }
};
// Есть абстрактный класс, для всех текстовых полей
public abstract class JTextComponent {
protected Document document;
}
// Есть интерфейс для работы обработки содержимого (отступы, поля, ширины и т.д.)
public interface Document { ... }
// Простой документ
public class PlainDocument implements Document { ... }
// Документ содержащий сложную HTML верстку
public class HTMLDocument implements Document { ... }
// В итоге, два разных объекта - текстовое поле
public class JTextField extends JTextComponent { ... }
// и текстовая панель может использовать разные документы.
public class JTextPane extends JTextComponent { ... }
// Типичный пример: Можем создавать множество легких объектов Font,
public class Font {
// которыe внутри себя хранят реальный класс для рендиринга
private transient Font2DHandle font2DHandle;
}
// Простой доступ к сложному объекту
JFrame jframe = new JFrame("frame");
// делаем простой вызов, который на самом деле дергает кучу других методов
jframe.show();
// Создаем ридер с кодировкой, InputStreamReader isr = new InputStreamReader(in, "Cp1251"); // и используем его буфферизованном ридер BufferedReader br = new BuffereReader(isr); // и всё это в итоге все равно просто Reader
// У нас есть класс-виджет
class Component {
public validate() { ... }
}
// и класс-контейнер.
class Container extends Component {
// При этом контейнером это тот же виджет
private Component[] childs;
// метод для валидации
public validate() {
...
// рекурсивно вызывает у всех потомков
for (Component c: childs) { c.validate(); }
}
}
}
// Определяется единый интерфейс для клиента и сервера
interface Hello extends Remote { public void hello(); }
// На клиентской стороне
Registry registry = LocateRegistry.getRegistry("hello");
// Получаем прокси объект, вызовы методов прокидываются на сервер
Hello rmiObj = (Hello) registry.lookup(name);
// Вызываем метод у прокси объекта
rmiObj.hello();
Behavior/Поведенческий
Chain of Responsobility
// есть фильтр
public interface Filter {
// внутри фильтра мы можем вызывать следующий фильтр
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chian)
throws IOException, ServletException;
}
// через интерфейс - цепочка фильтров
public interface FilterChain {
public void doFilter(ServletRequest req, ServletResponse resp)
throws IOException, ServletException;
}
Пример использования в стандартных классах я не нашел.
Типичный пример использования, отслеживание переключений различных кнопок
// Стратегию иногда Policy, т.е. линия поведения, курс, политика
public interface CookiePolicy {
// Стратегия - принимать любые куки
public static final CookiePolicy ACCEPT_ALL = new CookiePolicy() {
public boolean shouldAccept(URI uri, HttpCookie cookie) {
return true;
}
};
// Стратегия - не принимать никакие куки
public static final CookiePolicy ACCEPT_NONE = new CookiePolicy(){
public boolean shouldAccept(URI uri, HttpCookie cookie) {
return false;
}
};
// Стратегия - принимаем купи только с оригинального сервера
public static final CookiePolicy ACCEPT_ORIGINAL_SERVER =
new CookiePolicy() {
public boolean shouldAccept(URI uri, HttpCookie cookie) {
return HttpCookie.domainMatches(cookie.getDomain(), uri.getHost());
}
};
// Метод определяющий поведение системы
public boolean shouldAccept(URI uri, HttpCookie cookie);
}
// Класс за которым следят
public class Observable {
// Добавляем наблюдателя
public synchronized void addObserver(Observer o) { ... }
// Уведомляем всех что поменялись
public void notifyObservers(Object arg) { ... }
}
// интерфейс для слежки
public interface Observer {
// Этот метод вызывается когда наблюдаемый объект изменяется
void update(Observable o, Object arg);
}
// Есть интерфейс, который задает некоторые шаблонные методы
DefaultHandler handler = new DefaultHandler() {
// начало тэга
public void startElement(...) {...}
// конец тэга
public void endElement(...) {...}
// и т.д.
};
// Создаем парсер
SAXParser saxParser = factory.newSAXParser();
// Парсим файл, в результате вызываются шаблонные методы
saxParser.parse(file, handler);
Шаблон мементо обеспечивает возможность сохранять предыдущее состояние объекта.
Типичный пример использование возможно UNDO (откат) и REDO (повтор).
В шаблоне есть два объекта Originator — инициатор и Сaretaker — опекун. У Оригинатора есть свое состояние, а опекун может осущевствлять разные действия над ним.
Таким образом оригинатор должен иметь возможность предоставить информацию о текущем состоянии и востановиться по этой информации, а Опекун — должен хранить эту информацию и по необходимости инициировать восстановления Оригинатора.
Цель шаблона разделить объектную структуру и возможножность осуществлять различные действия над ней.
Используется секретная техника «двойной диспечер» (double-dispatch).
Есть интерфейс Visitor. в нем методы: visit(A a) , visit(B b), visit(C c)
Есть другой интерфейс Acceptor, в нем метод accept(Visitor v)
в нем делаем так:
class CustomVisitor implements Visitor {
public void visit(A a) {
System.out.println("делаем что-то с объектом:" + a);
}
...
}
class A implements Acceptor {
public void accept(Visitor v) {
v.visit(this);
}
}
A a = new A();
Vistor v = new Visitor();
a.accept(v);
Interpreter
Основаня идея заключается в том, что мы создаем опредленный класс для каждого выражения (число, строка, операция и т.д.) определенные для конкретного языка. Таким образом, полученная в результате разбора строка (Например:»2.3+3*4″) создает синтаксическое дерево — как в шаблоне Composite.
Interpreter определяет каким образом следует транслировать полученную языковую конструкцию.
Например можно использовать при разборе SQL-е подобных выражение.