Выделение общности (Factoring commonality)


Применение принципа "однажды и только однажды" порождает наиболее основные шаблоны, помещая меняющийся код в метод

Это может быть выражено двумя способами:

Стратегия (Strategy): выбор алгоритма во время выполнения

Стратегия также добавляет "Контекст", который может быть суррогатным классом, контролирующим выбор и использование определенной стратегии объекта - почти как Состояние! Вот как это выглядит:

//: strategy:StrategyPattern.java
package strategy;

import com.bruceeckel.util.*; // Arrays2.toString()

import junit.framework.*;

// Интерфейс стратегии:
interface FindMinima {
  
// Линия - это последовательность точек:
  
double[] algorithm(double[] line);
}

// Различные стратегии:
class LeastSquares implements FindMinima {
  
public double[] algorithm(double[] line) {
     
return new double[] { 1.1, 2.2 }; // Dummy
  
}
}

class NewtonsMethod implements FindMinima {
  
public double[] algorithm(double[] line) {
     
return new double[] { 3.3, 4.4 }; // Dummy
  
}
}

class Bisection implements FindMinima {
  
public double[] algorithm(double[] line) {
     
return new double[] { 5.5, 6.6 }; // Dummy
  
}
}

class ConjugateGradient implements FindMinima {
  
public double[] algorithm(double[] line) {
     
return new double[] { 3.3, 4.4 }; // Dummy
  
}
}

// "Контекст" управляет стратегией:
class MinimaSolver {
  
private FindMinima strategy;
  
  
public MinimaSolver(FindMinima strat) {
     
strategy = strat;
  
}
  
  
double[] minima(double[] line) {
     
return strategy.algorithm(line);
  
}
  
  
void changeAlgorithm(FindMinima newAlgorithm) {
     
strategy = newAlgorithm;
  
}
}

public class StrategyPattern extends TestCase {
  
MinimaSolver solver = new MinimaSolver(new LeastSquares());
  
double[] line = { 1.0, 2.0, 1.0, 2.0, -1.0, 3.0, 4.0, 5.0, 4.0 };
  
  
public void test() {
     
System.out.println(Arrays2.toString(solver.minima(line)));
      solver.changeAlgorithm
(new Bisection());
      System.out.println
(Arrays2.toString(solver.minima(line)));
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(StrategyPattern.class);
  
}
}
// /:~

Обратите внимание на сходство с шаблононным методом - шаблонный метод требует различия, так что он имеет более одного метода для вызова, что делает вещи фрагментированными. Однако есть большая вероятность, что объект стратегии будет иметь более одного вызываемого метода; рассмотрите систему выполнения заказов Shalloway с информацией страны в каждой стратегии.

Пример стратегии из JDK: сравнитель объектов.

Политика (Policy): обобщенная стратегия

Хотя GoF говорит, что Политика - это просто другое имя стратегии, они используют Стратегию, неявно предлагая единственный метод в объекте стратегии - так что вы можете вынести ваш меняющийся алгоритм, как единый кусок кода.

Другое [6] использование Политики означает, что объект, имеющий множественные методы, может отличаться от класса к классу. Это дает большую гибкость, чем ограничение единственным методом.

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

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

Шаблонный метод (Template method)

Рабочее пространство приложения позволяет вам наследовать от класса или набора классов и создавать новое приложение, повторно используя большую часть кода существующего класса и перегружая один или несколько методов для того, чтобы приложение удовлетворяло вашим требованиям. Фундаментальной концепцией рабочего пространства приложения является Шаблонный Метод, который обычно прячется под оберткой и управляет приложением, вызывая различные методы базового класса (некоторые из них вы можете перегрузить при создании приложения).

Например, когда бы вы не создавали апплет, вы используете рабочее пространство приложения: вы наследуете от JApplet, а затем перегружаете init(). Механизм апплета (который является Шаблонным Методом) делает остальное включая рисование на экране, цикл обработки событий, слежение за изменением размера и т.п.

Важнейшая характеристика Шаблонного Метода в том, что он определяется в базовом классе и не может быть изменен. Иногда это частный метод (private), но фактически он всегда имеет модификатор final. Он вызывает другой метод базового класса (который вы перегрузили), чтобы выполнить свою работу, но обычно он вызывает только часть процесса инициализации (и поэтому клиентскому программисту нет необходимости вызывать его напрямую).

//: templatemethod:TemplateMethod.java
// Простая демонстрация Шаблонного Метода.
package templatemethod;

import junit.framework.*;

abstract class ApplicationFramework {
  
public ApplicationFramework() {
     
templateMethod(); // Опасность!
  
}
  
  
abstract void customize1();
  
  
abstract void customize2();
  
  
final void templateMethod() {
     
for (int i = 0; i < 5; i++) {
        
customize1();
         customize2
();
     
}
   }
}

// Создание нового "приложения":
class MyApp extends ApplicationFramework {
  
void customize1() {
     
System.out.print("Hello ");
  
}
  
  
void customize2() {
     
System.out.println("World!");
  
}
}

public class TemplateMethod extends TestCase {
  
MyApp app = new MyApp();
  
  
public void test() {
     
// Конструктор MyApp делает всю работу.
      // Здесь необходимо убедится, что все завершается
      // без выбрасывания исключений.
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(TemplateMethod.class);
  
}
}
// /:~

Конструктор базового класса отвечает за выполнение необходимой инициализации, а затем запускает "машину" (шаблонный метод), которая запускает приложение (в GUI приложении такой "машиной" является главный цикл событий). Клиентский программист просто предоставляет определение для методов customize1( ) и customize2( ), и "приложение" готово к запуску.

Упражнения

  1. Создайте рабочую структуру, которая принимает список имен файлов из командной строки. Она открывает каждый файл, за исключением последнего, для чтения, а последний для записи. Эта рабочая структура обрабатывает каждый входящий файл, используя неопределенную политику, а затем пишет результат в последний файл. Наследуйте от этой рабочей структуры для создания двух разный приложений:
    1. Первое конвертирует все литеры каждого файла в верхний регистр.
    2. Второе ищет файлы, в которых содержатся слова из первого файла.