Чи існує схема дизайну, щоб усунути необхідність перевірки на наявність прапорів?


28

Я збираюся зберегти деякий рядок корисного навантаження в базі даних. У мене є дві глобальні конфігурації:

  • шифрування
  • стиснення

Вони можуть бути включені або відключені за допомогою конфігурації таким чином, що або один з них увімкнено, обидва ввімкнено або обидва відключені.

Моя поточна реалізація така:

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

Я думаю про візерунок «Декоратор». Це правильний вибір, чи є краща альтернатива?


5
Що не так з тим, що у вас зараз є? Чи можуть вимоги змінити цю функціональність? IE, чи можуть бути нові ifзаяви?
Даррен Янг

Ні, я розглядаю будь-яке інше рішення для поліпшення коду.
Даміт Ганегода

46
Ти йдеш про це назад. Ви не знайдете візерунок, тоді напишіть код, щоб відповідати шаблону. Ви пишете код відповідно до своїх вимог, а потім необов'язково використовуєте шаблон для опису свого коду.
Гонки легкості з Монікою

1
зауважте, якщо ви вважаєте, що ваше запитання справді є дублікатом цього питання , то, як запитувач , у вас є можливість "перекрити" нещодавно повторне відкриття та одноразово закрити його як таке. Я зробив це на деякі власні питання, і це працює як шарм. Ось як я це зробив, 3 прості кроки - єдина відмінність від моїх "інструкцій" полягає в тому, що оскільки у вас менше 3К повторень, вам доведеться пройти діалогове вікно прапора, щоб перейти до опції "дублікат"
gnat

8
@LightnessRacesinOrbit: Існує деяка правда в тому, що ви говорите, але цілком доцільно запитати, чи є кращий спосіб структурування коду, і цілком розумно застосовувати схему дизайну для опису запропонованої кращої структури. (Тим НЕ менше, я згоден , що це чимось на зразок проблеми XY , щоб задати для дизайну шаблону , коли то , що ви хочете це дизайн , який може або не може строго слідувати якомусь - або добре відомим шаблоном.) Крім того , це законно для «шаблонів» в трохи вплине на ваш код, оскільки, якщо ви використовуєте відомий зразок, часто має сенс відповідно називати ваші компоненти.
ruakh

Відповіді:


15

При розробці коду у вас завжди є два варіанти.

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

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

Але що буде, якби ви вирішили зробити це педантичним способом і насправді вирішили проблему з моделями дизайну так, як ви цього хотіли?

Ви можете переглядати такий процес:

При розробці коду OO, більшість ifs, які знаходяться в коді, не повинні бути там. Звичайно, якщо ви хочете порівняти два скалярні типи, наприклад, ints або floats, у вас, швидше за все, є if, але якщо ви хочете змінити процедури, засновані на конфігурації, ви можете використовувати поліморфізм для досягнення того, що ви хочете, переміщення рішень ( ifs) від вашої ділової логіки до місця, де об'єкти інстанційні - до заводів .

На сьогоднішній день ваш процес може пройти 4 окремі шляхи:

  1. dataне шифрується і не стискається (нічого не дзвоніть, повертайтеся data)
  2. dataстискається (дзвоніть compress(data)і повертайте його)
  3. dataзашифровано (зателефонуйте encrypt(data)та поверніть його)
  4. dataстискається та шифрується (дзвоніть encrypt(compress(data))та повертайте)

Просто переглянувши 4 шляхи, ви виявите проблему.

У вас є один процес, який викликає 3 (теоретично 4, якщо ви рахуєте, що не викликаєте нічого, як один) різні методи, які маніпулюють даними, а потім повертають їх. Методи мають різні назви , різні так звані public 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 Framework від Google), у вашій бізнес-логіці ви не переймаєтесь вибором правильного класу, який потрібно вводити. Тому що ви знаєте, що ваші фабрики працюють і доставлять те, що просять.

Чи потрібно мати усі ці класи, інтерфейси тощо?

Це повертає нас до початку.

Якщо в OOP ви вибираєте шлях використання поліморфізму, дійсно хочете використовувати дизайнерські зразки, хочете використовувати особливості мови та / або хочете слідувати, щоб все було об'єктною ідеологією, то це так. І навіть тоді цей приклад навіть не відображає всіх фабрик, які вам знадобляться, і якщо ви повинні переробляти Compressionта Encryptionкласи та робити інтерфейси замість них, ви також повинні включати їх реалізацію.

Зрештою, у вас опиняються сотні маленьких класів та інтерфейсів, орієнтованих на дуже конкретні речі. Що не обов'язково погано, але може бути не найкращим рішенням для вас, якщо все, що ви хочете, - зробити щось таке просто, як додавання двох чисел.

Якщо ви хочете зробити це швидко і швидко, ви можете захопити рішення Ixrec , якому принаймні вдалося ліквідувати else ifі elseблоки, які, на мій погляд, навіть в рази гірші за рівнину if.

Врахуйте, це мій спосіб зробити гарний дизайн OO. Кодування інтерфейсів, а не реалізацій, саме так я робив це протягом останніх кількох років, і саме цей підхід мені найбільше подобається.

Мені особисто більше подобається програмування if-less, і я б більше цінував довше рішення над 5 рядками коду. Це те, як я звик розробляти код і мені дуже зручно його читати.


Оновлення 2: Була бурхлива дискусія щодо першої версії мого рішення. Дискусія здебільшого викликана мною, за що я вибачаюся.

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


28
Я не заявив, але обґрунтуванням може бути смішна кількість нових класів / інтерфейсів, щоб зробити те, що оригінальний код робив у 8 рядках (а інша відповідь - у 5). На мою думку, єдине, що вона досягає, - це збільшення кривої навчання коду.
Маурісі

6
@Maurycy Запитав ОП, щоб спробувати знайти свою проблему за допомогою загальних моделей дизайну, якщо таке рішення існує. Чи моє рішення довше, ніж його чи код Ixrec? Це є. Я це визнаю. Чи вирішує моє рішення його проблему за допомогою шаблонів дизайну і таким чином відповідає на його запитання, а також ефективно видаляючи з процесу все необхідне? Це робить. Іксрек - ні.
Енді

26
Я вважаю, що чіткий, надійний, лаконічний, ефективний та бездоганний написання коду - це шлях. Якби я мав долар за кожен раз, коли хтось цитував SOLID або котирував програмний зразок, не чітко сформулювавши свої цілі та обґрунтування, я був би багатим чоловіком.
Роберт Харві

12
Я думаю, що тут я бачу два питання. По- перше, що Compressionі Encryptionінтерфейси здаються абсолютно зайвими. Я не впевнений, чи ти припускаєш, що вони якимось чином необхідні в процесі декорування, або просто мається на увазі, що вони представляють витягнуті концепції. Друге питання полягає в тому, що створення класу подібного CompressionEncryptionDecoratorпризводить до того ж виду комбінаторного вибуху, що і умова ОП. У запропонованому коді я також недостатньо чітко бачу шаблон декоратора.
cbojar

5
Дебати про SOLID vs. простих начебто не вистачають суті: цей код не є жодним, і він також не використовує шаблон декоратора. Код не є автоматично ТВОРЧИМ лише тому, що він використовує купу інтерфейсів. Введення залежності в інтерфейс DataProcessing якось приємно; все інше зайве. SOLID - це питання архітектурного рівня, спрямований на те, щоб добре впоратися зі змінами. ОП не дав жодної інформації про його архітектуру і про те, як він очікує зміни коду, тому ми не можемо навіть обговорити SOLID у відповіді.
Карл Лет

120

Єдина проблема, яку я бачу з вашим поточним кодом, - це ризик комбінаторного вибуху, оскільки ви додаєте більше налаштувань, які можна легко усунути, структуруючи код приблизно так:

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

Мені невідома жодна «модель дизайну» чи «ідіома», яку можна вважати прикладом.


18
@DamithGanegoda Nope, якщо ви уважно прочитаєте мій код, то побачите, що в цьому випадку це робить саме те саме. Ось чому elseміж двома моїми заявами немає, і чому я призначаю dataкожен раз. Якщо обидва прапори є істинними, то компресується () виконується, а потім шифрується () виконується за результатом компресії (), як і ви хочете.
Іксрек

14
@DavidPacker З технічної точки зору, це робить кожне твердження if у кожній мові програмування. Я пішов на простоту, оскільки це виглядало як проблема, де відповідь була дуже простою. Ваше рішення також є дійсним, але особисто я би його врятував, коли у мене є набагато більше двох булевих прапорів, щоб турбуватися.
Іксрек

15
@DavidPacker: правильність не визначається тим, наскільки добре код дотримується певного керівництва якогось автора щодо певної ідеології програмування. Правильно: "чи робить код те, що він повинен робити, і чи був він реалізований у розумний проміжок часу". Якщо є сенс зробити це «неправильним шляхом», то неправильний шлях - це правильний шлях, оскільки час - це гроші.
whatsisname

9
@DavidPacker: Якщо я був на посаді ОП і задав це питання, гонка легкості в коментарі Orbit - це те, що мені дійсно потрібно. "Пошук рішення за допомогою шаблонів дизайну" вже починається з неправильної стопи.
whatsisname

6
@DavidPacker Насправді, якщо ви уважно читаєте питання, воно не наполягає на шаблоні. У ньому сказано: "Я думаю про модель" Декоратор ". Це правильний вибір, чи є краща альтернатива?" . Ви звернулися до першого речення в моїй цитаті, але не до другого. Інші люди взяли підхід, що ні, це не правильний вибір. Тоді ви не можете стверджувати, що тільки ваше відповідає на питання.
Джон Бентлі

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

Як бачите, додавання команд / перетворень у список дозволяє виконувати послідовно. Очевидно, він виконає обидва, або лише один з них залежить від того, що ви помістите у список, якщо це не передбачено умовами.

Очевидно, що умовні умови опиняться на якійсь фабриці, яка поєднує список команд.

РЕДАКТУЙТЕ для коментаря @ texacre:

Існує багато способів уникнути, якщо умови у творчій частині рішення, візьмемо для прикладу настільний графічний інтерфейс програми . Ви можете мати прапорці для параметрів стискання та шифрування. У on clicразі виникнення цих прапорців ви створюєте відповідну команду та додаєте її до списку, або вилучаєте зі списку, якщо ви знімаєте цей параметр.


Якщо ви не можете навести приклад "якоїсь фабрики, яка поєднує список команд" без коду, який по суті схожий на відповідь Іксрека, то IMO це не відповідає на питання. Це забезпечує кращий спосіб реалізації функцій стиснення та шифрування, але не як уникнути прапорів.
thexacre

@thexacre Я додав приклад.
Тулен Кордова

Отже, у слухача подій у вашому прапорець у вас є "якщо checkbox.ticked тоді додати команду"? Мені здається, що ти просто пересуваєш прапор, якщо заяви навколо ...
thexacre

@thexacre Ні, один слухач для кожного прапорця. У події клацання точно commands.add(new EncryptCommand()); або commands.add(new CompressCommand());відповідно.
Тулен Кордова

А що з обробкою зняття галочки? Приблизно в кожному мовному інструментарії мови / інтерфейсу, з яким я стикався, вам все одно потрібно буде перевірити стан прапорця у слухачі подій. Я погоджуюсь, що це кращий зразок, але це не уникне потреби в принципі, якщо прапор щось десь робить.
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])

ТІЛЬКО змінюється, якщо ми хочемо додати нову функцію, скажімо, "налагодження друку", це наступне:

(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

Як функція заповнюється лише тим, що включає функції, які потрібно застосовувати, не використовуючи по суті, ряд операторів if, так чи інакше?
thexacre

Рядок funcs-to-run-here (map options funcs)виконує фільтрацію, вибираючи таким чином набір функцій, які потрібно застосувати. Можливо, я повинен оновити відповідь і перейти трохи детальніше.
NiklasJ

5

[По суті, моя відповідь - це продовження відповіді від @Ixrec вище . ]

Важливе питання: чи збільшиться кількість чітких комбінацій, які потрібно висвітлити? Ви краще знаєте свій предметний домен. Це ваше рішення.
Чи може кількість варіантів зростати? Ну, це немислимо. Наприклад, вам може знадобитися розміщення більше різних алгоритмів шифрування.

Якщо ви передбачаєте, що кількість різних комбінацій буде зростати, то модель Стратегії може вам допомогти. Він призначений для інкапсуляції алгоритмів та надання взаємозамінного інтерфейсу до викликового коду. Ви все одно матимете невелику кількість логіки, коли створите (інстанціювати) відповідну стратегію для кожного конкретного рядка.

Ви вище прокоментували, що не очікуєте, що вимоги зміняться. Якщо ви не очікуєте, що кількість варіантів зросте (або ви зможете відкласти це рефакторинг), то дотримуйтесь логіки такою, якою вона є. Наразі у вас є невелика і керована кількість логіки. (Можливо, напишіть собі коментарі у коментарях щодо можливого рефакторингу на стратегію.)


1

Один із способів зробити це в масштабі:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.