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-е подобных выражение.