Есть ли шаблон дизайна, который устраняет необходимость проверять флаги?

28

Я собираюсь сохранить некоторую полезную нагрузку строки в базе данных. У меня есть две глобальные конфигурации:

  • шифрование
  • компрессия

Они могут быть включены или отключены с помощью конфигурации таким образом, что либо только один из них включен, либо включены, либо оба отключены.

Моя текущая реализация такова:

if (encryptionEnable && !compressEnable) {
    encrypt(data);
} else if (!encryptionEnable && compressEnable) {
    compress(data);
} else if (encryptionEnable && compressEnable) {
    encrypt(compress(data));
} else {
  data;
}

Я думаю о шаблоне Decorator. Это правильный выбор или есть лучшая альтернатива?

Дамит Ганегода
источник
5
Что не так с тем, что у вас сейчас есть? Могут ли требования измениться для этой функциональности? IE, могут ли быть новые ifзаявления?
Даррен Янг
Нет, я смотрю на любое другое решение для улучшения кода.
Дамит Ганегода
46
Вы идете об этом задом наперед. Вы не найдете шаблон, а затем напишите код, который будет соответствовать шаблону. Вы пишете код в соответствии с вашими требованиями, а затем по желанию используете шаблон для описания вашего кода.
Легкость гонок с Моникой
1
обратите внимание, если вы считаете, что ваш вопрос действительно является дубликатом этого вопроса, то у вас, спрашивающего, есть возможность «переопределить» недавнее повторное открытие и единолично закрыть его как таковой. Я сделал это с некоторыми из моих собственных вопросов, и это работает как очарование. Вот как я это сделал, 3 простых шага - единственное отличие от моих «инструкций» состоит в том, что, поскольку у вас менее 3К повторений, вам придется пройти через диалоговое окно флага, чтобы перейти к опции «дублировать»
gnat
8
@LightnessRacesinOrbit: В том, что вы говорите, есть доля правды, но совершенно разумно спросить, есть ли лучший способ структурировать свой код, и совершенно разумно вызвать шаблон проектирования, чтобы описать предложенную лучшую структуру. (Тем не менее, я согласен, что это небольшая проблема XY - запрашивать шаблон проектирования , когда то, что вам нужно, это дизайн , который может строго следовать или не следовать любому известному шаблону.) Кроме того, для «шаблонов» допустимо слегка повлияйте на ваш код, в том случае, если вы используете известный шаблон, часто имеет смысл называть ваши компоненты соответственно
Руах

Ответы:

15

При разработке кода у вас всегда есть два варианта.

  1. просто сделайте это, и в этом случае практически любое решение будет работать для вас
  2. быть педантичным и разработать решение, которое использует причуды языка и его идеологию (в данном случае ОО-языки - использование полиморфизма в качестве средства для принятия решения)

Я не собираюсь сосредотачиваться на первом из двух, потому что на самом деле нечего сказать. Если вы просто хотите заставить его работать, вы можете оставить код как есть.

Но что произойдет, если вы решите сделать это педантично и действительно решите проблему с шаблонами проектирования, так, как вы этого хотели?

Вы могли бы смотреть на следующий процесс:

При разработке ОО-кода, большинство из тех, ifкоторые находятся в коде, не обязательно должны быть там. Естественно, если вы хотите сравнить два скалярных типа, таких как ints или floats, у вас, вероятно, есть if, но если вы хотите изменить процедуры на основе конфигурации, вы можете использовать полиморфизм для достижения того, что вы хотите, перенести решения ( ifs) от вашей бизнес-логики до места, где создаются объекты - на фабрики .

На данный момент ваш процесс может пройти 4 отдельных пути:

  1. dataне шифруется и не сжимается (ничего не вызывать, возвращать data)
  2. dataсжат (позвоните compress(data)и верните его)
  3. dataзашифрован (позвоните encrypt(data)и верните его)
  4. dataсжат и зашифрован (позвоните encrypt(compress(data))и верните его)

Просто глядя на 4 пути, вы обнаружите проблему.

У вас есть один процесс, который вызывает 3 (теоретически 4, если вы считаете, что ничего не вызываете как один) разные методы, которые манипулируют данными, а затем возвращают их. Методы имеют разные имена , разные так называемые публичные API (способ, которым методы передают свое поведение).

Используя шаблон адаптера , мы можем решить возникновение конфликта имен (мы можем объединить публичный API). Проще говоря, адаптер помогает двум несовместимым интерфейсам работать вместе. Кроме того, адаптер работает, определяя новый интерфейс адаптера, который классы пытаются объединить в своем API.

Это не конкретный язык. Это универсальный подход, любое ключевое слово для представления может быть любого типа, в языке, подобном C #, вы можете заменить его на generics ( <T>).

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

class Compression
{
    Compress(data : any) : any { ... }
}

class Encryption
{
    Encrypt(data : any) : any { ... }
}

В корпоративном мире даже эти конкретные классы с большой вероятностью будут заменены интерфейсами, например, classключевое слово будет заменено на interface(если вы имеете дело с такими языками, как C #, Java и / или PHP) или classключевое слово останется, но Compressи Encryptметоды будут определены как чисто виртуальные , если вы будете кодировать на C ++.

Чтобы сделать адаптер, мы определяем общий интерфейс.

interface DataProcessing
{
    Process(data : any) : any;
}

Затем мы должны предоставить реализации интерфейса, чтобы сделать его полезным.

// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
    public Process(data : any) : any
    {
        return data;
    }
}

// when only compression is enabled
class CompressionAdapter : DataProcessing
{
    private compression : Compression;

    public Process(data : any) : any
    {
        return this.compression.Compress(data);
    }
}

// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
    private encryption : Encryption;

    public Process(data : any) : any
    {
        return this.encryption.Encrypt(data);
    }
}

// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
    private compression : Compression;
    private encryption : Encryption;

    public Process(data : any) : any
    {
        return this.encryption.Encrypt(
            this.compression.Compress(data)
        );
    }
}

Делая это, вы получаете 4 класса, каждый из которых делает что-то совершенно другое, но каждый из них предоставляет один и тот же публичный API. ProcessМетод.

В вашей бизнес-логике, где вы имеете дело с решением «нет / шифрование / сжатие / оба», вы создадите свой объект так, чтобы он зависел от DataProcessingинтерфейса, который мы проектировали ранее.

class DataService
{
    private dataProcessing : DataProcessing;

    public DataService(dataProcessing : DataProcessing)
    {
        this.dataProcessing = dataProcessing;
    }
}

Сам процесс может быть таким простым:

public ComplicatedProcess(data : any) : any
{
    data = this.dataProcessing.Process(data);

    // ... perhaps work with the data

    return data;
}

Нет больше условий. Класс DataServiceне имеет представления о том, что на самом деле будет сделано с данными, когда они будут переданы dataProcessingчлену, и он на самом деле не заботится об этом, это не его обязанность.

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

Так что, делая это таким образом, я никогда больше не буду ifв своем коде?

Нет. Вы менее склонны иметь условные выражения в своей бизнес-логике, но они все равно должны быть где-то. Место это ваши фабрики.

И это хорошо. Вы разделяете проблемы создания и фактического использования кода. Если вы делаете свои фабрики надежными (в Java вы можете даже пойти на то, чтобы использовать что-то вроде фреймворка Guice от Google), в вашей бизнес-логике вас не беспокоит выбор правильного класса для внедрения. Потому что вы знаете, что ваши фабрики работают и доставят то, что просят.

Нужно ли иметь все эти классы, интерфейсы и т. Д.?

Это возвращает нас к началу.

В ООП, если вы выбираете путь для использования полиморфизма, действительно хотите использовать шаблоны проектирования, хотите использовать возможности языка и / или хотите следовать всему, что является объектной идеологией, тогда это так. И даже тогда, этот пример не даже показать все заводы , которые вы собираетесь потребность , и если вы были рефакторить Compressionи Encryptionклассы и сделать их интерфейсы вместо этого, вы должны включить их реализацию , а также.

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

Если вы хотите , чтобы сделать это и быстро, вы можете получить решение Ixrec в , который , по крайней мере удалось устранить else ifи elseблоки, которые, на мой взгляд, даже чуть - чуть хуже , чем в равнине if.

Примите во внимание, что это мой способ сделать хороший дизайн ОО. Кодирование интерфейсов, а не реализаций, это то, как я делал это в течение последних нескольких лет, и этот подход мне наиболее удобен.

Мне лично больше нравится программирование if-less, и я был бы очень признателен за более длинное решение из 5 строк кода. Я привык к проектированию кода, и мне очень удобно его читать.


Обновление 2: была дикая дискуссия о первой версии моего решения. Обсуждение в основном вызвано мной, за что я прошу прощения.

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

Энди
источник
28
Я не отрицал, но обоснованием может быть смешное количество новых классов / интерфейсов для выполнения того, что исходный код делал в 8 строк (а другой ответ - в 5). По моему мнению, единственное, что он делает, - это увеличение кривой обучения для кода.
Мориси
6
@Maurycy То, что попросил ОП, было попытаться найти решение его проблемы, используя общие шаблоны проектирования, если такое решение существует. Мое решение длиннее, чем его или Ixrec код? Это. Я признаю это. Мое решение решает его проблему, используя шаблоны проектирования и, таким образом, отвечает на его вопрос, а также эффективно удаляет все необходимые if из процесса? Оно делает. Икрека нет.
Энди
26
Я считаю, что написание кода, который является ясным, надежным, лаконичным, производительным и обслуживаемым, - это путь. Если бы у меня был доллар за каждый раз, когда кто-то цитировал SOLID или цитировал образец программного обеспечения, не четко формулируя свои цели и обоснование, я был бы богатым человеком.
Роберт Харви
12
Я думаю, что у меня есть две проблемы, которые я вижу здесь. Во- первых, что Compressionи Encryptionинтерфейсы кажутся совершенно излишними. Я не уверен, предлагаете ли вы, что они каким-то образом необходимы для процесса украшения, или просто подразумеваете, что они представляют собой извлеченные понятия. Вторая проблема заключается в том, что создание подобного класса CompressionEncryptionDecoratorприводит к тому же виду комбинаторного взрыва, что и условные выражения ОП. Я также не вижу шаблон декоратора достаточно ясно в предложенном коде.
cbojar
5
В дебатах о SOLID и простых словах не хватает смысла: этот код не является ни тем, ни другим, и в нем также не используется шаблон декоратора. Код не является автоматически SOLID только потому, что он использует несколько интерфейсов. Внедрение зависимостей интерфейса DataProcessing довольно приятно; все остальное лишнее. SOLID - это задача архитектурного уровня, нацеленная на хорошую обработку изменений. OP не дал никакой информации ни о своей архитектуре, ни о том, как он ожидает, что его код изменится, поэтому мы не можем даже обсудить SOLID в ответе.
Карл Лет
120

Единственная проблема, с которой я сталкиваюсь в вашем текущем коде, - это риск комбинаторного взрыва, когда вы добавляете больше настроек, которые можно легко уменьшить, структурировав код следующим образом:

if(compressEnable){
  data = compress(data);
}
if(encryptionEnable) {
  data = encrypt(data);
}
return data;

Я не знаю ни о каком «шаблоне дизайна» или «идиоме», который можно было бы считать примером.

Ixrec
источник
18
@DamithGanegoda Нет, если вы внимательно прочитаете мой код, то увидите, что в этом случае он делает то же самое. Вот почему elseмежду моими двумя утверждениями if нет и почему я назначаю dataкаждый раз. Если оба флага имеют значение true, тогда compress () выполняется, а encrypt () выполняется на результат compress (), как вы хотите.
Ixrec
14
@DavidPacker Технически, так же как и каждый оператор if на каждом языке программирования. Я пошел на простоту, так как это выглядело как проблема, где уместен очень простой ответ. Ваше решение также действительно, но лично я бы сохранил его, когда у меня будет более двух логических флагов, о которых нужно беспокоиться.
Ixrec
15
@DavidPacker: корректность не определяется тем, насколько хорошо автор придерживается какого-то руководства относительно некоторой идеологии программирования. Правильным является то, «выполняет ли код то, что должен, и был ли он реализован в разумные сроки». Если имеет смысл делать это «неправильным путем», то неправильный путь - это правильный путь, потому что время - деньги.
whatsisname
9
@DavidPacker: Если бы я был на позиции ОП и задавал этот вопрос, мне нужна действительно легкая гонка в комментарии Орбиты. «Поиск решения с использованием шаблонов проектирования» уже начинается не с той ноги.
whatsisname
6
@DavidPacker На самом деле, если вы прочитаете вопрос более внимательно, он не настаивает на шаблоне. В нем говорится: «Я думаю о шаблоне Decorator. Это правильный выбор или, возможно, есть лучшая альтернатива?» , Вы обратились к первому предложению в моей цитате, но не ко второму. Другие люди приняли такой подход, что нет, это не правильный выбор. Вы не можете тогда утверждать, что только ваш ответит на вопрос.
Джон Бентли
12

Я полагаю, что ваш вопрос ищет не практичности, и в этом случае ответ lxrec является правильным, а узнать о шаблонах проектирования.

Очевидно, что шаблон команды является излишним для такой тривиальной проблемы, как та, которую вы предлагаете, но для иллюстрации здесь это звучит так:

public interface Command {
    public String transform(String s);
}

public class CompressCommand implements Command {
    @Override
    public String transform(String s) {
        String compressedString=null;
        //Compression code here
        return compressedString;
    }
}

public class EncryptCommand implements Command {
    @Override
    public String transform(String s) {
        String EncrytedString=null;
        // Encryption code goes here
        return null;
    }

}

public class Test {
    public static void main(String[] args) {
        List<Command> commands = new ArrayList<Command>();
        commands.add(new CompressCommand());
        commands.add(new EncryptCommand()); 
        String myString="Test String";
        for (Command c: commands){
            myString = c.transform(myString);
        }
        // now myString can be stored in the database
    }
}

Как видите, размещение команд / преобразований в списке позволяет выполнять их последовательно. Очевидно, он будет выполнять оба или только один из них в зависимости от того, что вы поместили в список без условий if.

Очевидно, что условные выражения окажутся в какой-то фабрике, которая собирает список команд.

РЕДАКТИРОВАТЬ для комментария @ texacre:

Есть много способов избежать условий if в творческой части решения, давайте возьмем, например, приложение с графическим интерфейсом для настольных компьютеров . Вы можете иметь флажки для параметров сжатия и шифрования. В on clicслучае этих флажков вы создаете соответствующую команду и добавляете ее в список или удаляете из списка, если отмените выбор опции.

Тулаинс Кордова
источник
Если вы не предоставите пример «какой-то фабрики, которая собирает список команд» без кода, который по сути выглядит как ответ Ixrec, то IMO не отвечает на этот вопрос. Это обеспечивает лучший способ реализации функций сжатия и шифрования, но не позволяет избежать флагов.
thexacre
@thexacre Я добавил пример.
Тулаинс Кордова
Итак, в вашем прослушивателе событий checkbox у вас есть «if checkbox.ticked затем добавить команду»? Мне кажется, что вы просто тасуете флаг, если заявления вокруг ...
thexacre
@thexacre Нет, один слушатель для каждого флажка. В событии клика только commands.add(new EncryptCommand()); или commands.add(new CompressCommand());соответственно.
Тулаинс Кордова
Как насчет обработки снятия флажка? Практически во всех языковых / UI-инструментальных средствах, с которыми я сталкивался, вам все равно нужно проверять состояние флажка в приемнике событий. Я согласен, что это лучший шаблон, но он не исключает необходимости в основном, если флаг что-то делает.
thexacre
7

Я думаю, что «шаблоны дизайна» излишне ориентированы на «шаблоны» и полностью избегают гораздо более простых идей. Здесь мы говорим о (простом) конвейере данных.

Я хотел бы попытаться сделать это в ближайшем будущем. Любой другой язык, где функции первоклассны, вероятно, тоже подойдет. Может быть, я мог бы позже привести пример на C #, но это не так хорошо. Моим способом решения этой проблемы были бы следующие шаги с некоторыми объяснениями для не-клюжанцев:

1. Представьте набор преобразований.

(def transformations { :encrypt  (fn [data] ... ) 
                       :compress (fn [data] ... )})

Это карта, то есть таблица поиска / словарь / что угодно, от ключевых слов до функций. Другой пример (ключевые слова для строк):

(def employees { :A1 "Alice" 
                 :X9 "Bob"})

(employees :A1) ; => "Alice"
(:A1 employees) ; => "Alice"

Итак, написание (transformations :encrypt)или (:encrypt transformations)вернет функцию шифрования. ( (fn [data] ... )это просто лямбда-функция.)

2. Получить параметры в виде последовательности ключевых слов:

(defn do-processing [options data] ;function definition
  ...)

(do-processing [:encrypt :compress] data) ;call to function

3. Отфильтруйте все преобразования, используя предоставленные параметры.

(let [ transformations-to-run (map transformations options)] ... )

Пример:

(map employees [:A1]) ; => ["Alice"]
(map employees [:A1 :X9]) ; => ["Alice", "Bob"]

4. Объедините функции в одну:

(apply comp transformations-to-run)

Пример:

(comp f g h) ;=> f(g(h()))
(apply comp [f g h]) ;=> f(g(h()))

5. А потом вместе:

(def transformations { :encrypt  (fn [data] ... ) 
                       :compress (fn [data] ... )})

(defn do-processing [options data]
  (let [transformations-to-run (map transformations options)
        selected-transformations (apply comp transformations-to-run)] 
    (selected-transformations data)))

(do-processing [:encrypt :compress])

ЕДИНСТВЕННЫЕ изменения, если мы хотим добавить новую функцию, скажем, «debug-print», следующие:

(def transformations { :encrypt  (fn [data] ... ) 
                       :compress (fn [data] ... )
                       :debug-print (fn [data] ...) }) ;<--- here to add as option

(defn do-processing [options data]
  (let [transformations-to-run (map transformations options)
        selected-transformations (apply comp transformations-to-run)] 
    (selected-transformations data)))

(do-processing [:encrypt :compress :debug-print]) ;<-- here to use it
(do-processing [:compress :debug-print]) ;or like this
(do-processing [:encrypt]) ;or like this
NiklasJ
источник
Как заполнить функции, чтобы они включали только те функции, которые необходимо применить, не используя при этом последовательность операторов if тем или иным образом?
thexacre
Строка funcs-to-run-here (map options funcs)выполняет фильтрацию, таким образом выбирая набор функций для применения. Может быть, я должен обновить ответ и углубиться в детали.
NiklasJ
5

[По сути, мой ответ является продолжением ответа @Ixrec выше . ]

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

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

Вы прокомментировали выше, что не ожидаете изменения требований. Если вы не ожидаете, что число вариантов будет расти (или вы можете отложить этот рефакторинг), оставьте логику такой, какая она есть. В настоящее время у вас есть небольшое и управляемое количество логики. (Возможно, добавьте примечание к себе в комментариях о возможном рефакторинге к шаблону Стратегии.)

Ник Алексеев
источник
1

Один из способов сделать это в Scala:

val handleCompression: AnyRef => AnyRef = data => if (compressEnable) compress(data) else data
val handleEncryption: AnyRef => AnyRef = data => if (encryptionEnable) encrypt(data) else data
val handleData = handleCompression andThen handleEncryption
handleData(data)

Использование шаблона декоратора для достижения вышеуказанных целей (разделение логики отдельной обработки и способ их соединения) было бы слишком многословным.

Там, где вам потребуется шаблон проектирования для достижения этих целей проектирования в парадигме программирования ОО, функциональный язык предлагает встроенную поддержку, используя функции в качестве первоклассников (строки 1 и 2 в коде) и функциональную композицию (строка 3).

Сачин К
источник
Почему это лучше (или хуже), чем подход ОП? И / или что вы думаете об идее ОП использовать шаблон декоратора?
Каспер ван ден Берг
этот фрагмент кода лучше и явно касается порядка (сжатие перед шифрованием); избегает нежелательных интерфейсов
Rag