Расширение функциональности паттерна Стратегия

Я разрабатываю приложение, которое сравнивает файлы. Я решил использовать шаблон проектирования «Стратегия» для работы с разными форматами, поэтому у меня получилось что-то вроде этого:

public class Report {
   CompareStrategy strategy;
   ...
}


public interface CompareStrategy {
   int compare(InputStream A, InputStreamB);
}

Затем, естественно, я реализую метод сравнения для разных форматов файлов.

Теперь предположим, что я хочу добавить еще один метод, который имеет дело с определенными ограничениями для сравнения (например, опустить строку в случае файла Excel или csv или опустить узел в XML).

Было бы лучше:

  1. Добавить еще один метод в интерфейс и каждую реализацию (на данный момент их немного)
  2. Написать новый интерфейс, наследуемый от CompareStrategy, а затем реализовать его?

Второй вопрос: так как различия могут быть разных типов - можно ли сделать маркер interface Difference, чтобы включить что-то вроде:

int compareWithDifferences(..., Iterable<Difference> differences);

а затем продолжить определение того, что означает разница для конкретного формата файла?


person DCzo    schedule 01.08.2017    source источник
comment
Ваш проект, кажется, нарушает LSP, потому что в зависимости от того, что содержит входной поток, разные стратегии будут успешными или неудачными, и это было бы неожиданно, потому что замена одной стратегии на другую не должна влиять на правильность программы. Вы действительно должны задаться вопросом, приносит ли интерфейс вам пользу или нет. Если это так, то следует, по крайней мере, четко указать, что стратегия не может обрабатывать какой-либо контент, например. bool canCompare(a, b)   -  person plalx    schedule 01.08.2017
comment
Не могли бы вы уточнить? Разработанные мной стратегии тесно связаны с форматом файла (некоторые из них табличные, другие — на языках разметки). Использование InputStream предназначено для облегчения модульного тестирования.   -  person DCzo    schedule 01.08.2017
comment
Я имею в виду, что вы должны знать, какие экземпляры могут сравнивать какие типы файлов, что в первую очередь противоречит цели интерфейса, потому что приложение должно знать реализации и не может просто работать только с CompareStrategy, не заботясь о том, какая реализация является при условии. Если вам нужна расширяемость, то вам, скорее всего, нужен такой интерфейс, чтобы вы могли динамически регистрировать новые компараторы, но это не шаблон стратегии, и из интерфейса должно быть ясно, что вызов компаратора с неправильным содержимым может вызвать выброс.   -  person plalx    schedule 01.08.2017
comment
Но не правда ли, что шаблон Стратегия определяет другую стратегию для другого контекста? Это как раз тот случай, так как способ разбора файла не отрывается от формата файла. Я следую принципам, описанным здесь: sourcemaking.com/design_patterns/strategy.   -  person DCzo    schedule 01.08.2017
comment
Из вашего собственного источника... Стратегия определяет набор взаимозаменяемых алгоритмов. Это совсем не ваш случай, например, вы не можете взаимозаменяемо использовать XMLCompareStrategy с содержимым HTML. Поэтому strategy1.compare(a, b) может работать, а strategy2.compare(a, b) может не работать для тех же a и b. Этого не должно быть в случае с шаблоном стратегии.   -  person plalx    schedule 01.08.2017
comment
Вы правы, но я все же хотел бы иметь какой-нибудь аккуратный способ применить сравнение без использования раздутой инструкции переключения (вероятно, будет больше форматов файлов, чем я сейчас имею дело, и приложение будет сравнивать множество отчетов). Интерфейс позволяет мне вызывать что-то вроде: report.getStrategy.compare(a, b), не слишком беспокоясь о формате. Какой еще узор посоветуете?   -  person DCzo    schedule 01.08.2017
comment
Не обязательно другой шаблон, но я просто говорю, что это не реализация шаблона стратегии. Я бы, вероятно, просто выбрал интерфейс Comparator и добавил в интерфейс объявление throws, чтобы явно указать, что он может потерпеть неудачу.   -  person plalx    schedule 01.08.2017
comment
Вы имеете в виду какое-либо существующее исключение или мне нужно написать собственное?   -  person DCzo    schedule 01.08.2017
comment
Вероятно, пользовательский, если нет существующего, который передает смысл.   -  person plalx    schedule 01.08.2017
comment
Попробую этот способ, спасибо!   -  person DCzo    schedule 01.08.2017


Ответы (3)


Теперь предположим, что я хочу добавить еще один метод, который имеет дело с определенными ограничениями для сравнения (например, опустить строку в случае файла Excel или csv или опустить узел в XML).

Похоже, вам нужен Шаблон шаблона

Вы можете создать некоторый абстрактный класс, например

public abstract class XMLCompareStrategy implements CompareStrategy {

    public int compare(InputStream A, InputStreamB) {
        // several steps
        customMethod(...);
        // more steps
    }

    protected abstract ... customMethod(...);

}

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

person Pelocho    schedule 01.08.2017
comment
Хороший. Коротко и точно! - person GhostCat; 01.08.2017
comment
Итак (просто чтобы удовлетворить мою манию классификации;)) - это будет комбинация Стратегии (базовый интерфейс, реализованный для абстрактных классов для каждого формата) с Шаблоном (абстрактные классы, расширенные до конкретных классов в соответствии с их функциональностью). Это правильно? - person DCzo; 01.08.2017
comment
Ага. С помощью Strategy вы отделяете фактическую реализацию для каждого формата, а с помощью Template вы предоставляете базовую реализацию для каждого формата, а также предоставляете несколько точек настройки алгоритма. - person Pelocho; 01.08.2017
comment
1. Абстрактный класс — хорошее решение, если compare реализация является общей для всех реализующих классов. 2. Имейте в виду, что, используя абстрактный класс вместо интерфейса, вы теряете возможность расширять любой другой класс. - person SHG; 01.08.2017
comment
Если расширение класса становится проблемой, вы всегда можете избавиться от абстрактных методов, снова реализовав шаблон стратегии;) - person Pelocho; 01.08.2017

Ответ действительно зависит от ваших потребностей:

  • Если метод будет реализован всегда — добавьте его в существующий интерфейс. Если это только необязательный — создайте новый интерфейс, который расширит текущий, а затем реализованный класс может реализовать либо базовый интерфейс, либо дочерний интерфейс, если ему нужны оба метода.
  • Что касается вашего второго вопроса, то для меня это выглядит как чрезмерное проектирование, но опять же зависит от ваших потребностей.
person SHG    schedule 01.08.2017

Я думаю, вам следует написать другой интерфейс, наследующий от CompareStrategy. Так, если вам нужно compareWithDifferences(), вы можете, но вам не обязательно использовать этот интерфейс, вы все равно можете использовать более простой без каких-либо различий.

Как сказал Джонатан, если вы можете предвидеть трудности, подготовьтесь к ним. В таком случае, я думаю, вам следует подготовиться. Действительно, это не будет стоить вам много времени на создание другого интерфейса, и вам не придется позже проводить рефакторинг.

person Alexandre Thyvador    schedule 01.08.2017
comment
Подсказка: обязательно используйте встроенную проверку языка, которая есть в вашем браузере. - person GhostCat; 01.08.2017