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

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

Приемы объектно-ориентированного проектирования. Паттерны проектирования Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес

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

Стивен Стелтинг, Олав Маассен. Применение шаблонов Java. Библиотека профессионала

Из книг по шаблоном именно для Java-программистов читал "Применение шаблонов Java".

Описание шаблонов, которые я здесь привожу построено на следующем принципе.

Название шаблона - ссылка на статью в википдеии. Пример - фрагмент java-кода, где  этот шаблон может встречаться в стандартном Java API или в каком-то "бытовом" случае.

Для шаблонов, примеры которых я не смог найти место в стандартной реализации, приводиться просто его краткое описание. Другими словами там, где можно обойтись от использования "оригнальных" и "понятных" примеров ООП - CarFactory, Car, Ford или Fruit, Apple или Person, Student, Prepod и так далее, я старался использовать привычные для программиста StringBuilderMouseAdapter, Reader. Т.е. классы, объекты которых мы используем каждый день.

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

Creational/Порождающий

AbstractFactory

// Создает DOM Document Builder в зависимости от используемого парсера
DocumentBuilderFactory documentBuilderFactory = ...;
documentBuilderFactory.newDocumentBuilder();


FactoryMethod

// Возвращает разные Toolkit'ы для Windows, Linux, MacOS
Toolkit.getDefaultToolkit();

Builder

// Сначала добавляем разные значения,  потом строим строку
StringBuilder sb = new StringBuilder();
sb.append("Hello ");
sb.append(123);
String txt = sb.toString();

Prototype

//  Есть прототип из которого легко создавать копии объекты
Date protypeDate = ...;
// Получаем такую  же дату просто клонируя
Date date = protypeDate.clone();

Singleton

// Всегда возвращаем один класс  Console
System.console();

Structural/Структурный

Adapter

new  MouseAdapter() {
// Перекрываем только нужный нам  метод и используем объект как MouseListener
    public void  mouseClicked(MouseEvent e) { System.out.println("mouseClicked"); }
};

Bridge

// Есть абстрактный класс, для  всех текстовых полей
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 { ... }

Flyweight

// Типичный пример: Можем  создавать множество легких объектов Font,
public class Font {
    //  которыe внутри себя хранят реальный класс для рендиринга
    private  transient Font2DHandle font2DHandle;
}

Facade

// Простой доступ к сложному объекту
JFrame jframe = new JFrame("frame");
// делаем простой  вызов, который на самом деле дергает кучу других методов
jframe.show();

Decorator

// Создаем ридер с кодировкой,
InputStreamReader isr = new InputStreamReader(in, "Cp1251");
// и используем его буфферизованном ридер
BufferedReader br = new BuffereReader(isr);
// и всё это в итоге все равно просто Reader

Composite

// У нас есть класс-виджет
class Component {
    public validate() { ... }
}
// и класс-контейнер.
class Container extends Component {
    // При этом контейнером это тот же виджет
    private Component[] childs;
    // метод для валидации
    public validate() {
    ...
        // рекурсивно вызывает у всех потомков
        for (Component c: childs) { c.validate(); }
        }
    }
}

Proxy

// Определяется единый интерфейс для клиента и сервера
interface Hello extends Remote { public void hello(); }
// На клиентской стороне
Registry registry = LocateRegistry.getRegistry("hello");
// Получаем прокси объект, вызовы методов прокидываются на сервер
Hello rmiObj = (Hello) registry.lookup(name);
// Вызываем метод у прокси объекта
rmiObj.hello();

Behavior/Поведенческий

Chain of Responsobility

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;
}

Mediator

Пример использования в стандартных классах я не нашел.
Типичный пример использования, отслеживание переключений различных кнопок

Strategy

// Стратегию иногда 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);
}

Observer

// Класс за которым следят
public class Observable {
    // Добавляем наблюдателя
    public synchronized void addObserver(Observer o) { ... }

    //  Уведомляем всех что поменялись
    public void notifyObservers(Object arg) { ... }
}

// интерфейс для слежки
public interface Observer {
    // Этот метод вызывается когда наблюдаемый объект изменяется
    void update(Observable o, Object arg);
}

Template Method

// Есть интерфейс, который задает некоторые шаблонные методы
DefaultHandler handler = new DefaultHandler() {
    // начало тэга
    public void startElement(...) {...}
    // конец тэга
    public void endElement(...) {...}
    // и т.д.
};
// Создаем парсер
SAXParser saxParser = factory.newSAXParser();
// Парсим файл, в результате вызываются шаблонные методы
saxParser.parse(file, handler);

Memento

Шаблон мементо обеспечивает возможность сохранять предыдущее состояние объекта.
Типичный пример использование возможно UNDO (откат) и REDO (повтор).

В шаблоне есть два объекта Originator - инициатор и Сaretaker - опекун. У Оригинатора есть свое состояние, а опекун может осущевствлять разные действия над ним.
Таким образом оригинатор должен иметь возможность предоставить информацию о текущем состоянии и востановиться по этой информации, а Опекун - должен хранить эту информацию и по необходимости инициировать восстановления Оригинатора.

Visitor

Цель шаблона разделить объектную структуру и возможножность осуществлять различные действия над ней.
Используется секретная техника "двойной диспечер" (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-е подобных выражение.