При розробці коду у вас завжди є два варіанти.
- просто зробіть це, і в цьому випадку для вас підійде практично будь-яке рішення
- бути педантичним і розробити рішення, яке експлуатує вигадки мови та її ідеологію (мови OO в даному випадку - використання поліморфізму як засобу для прийняття рішення)
Я не збираюся зосереджуватися на першому з двох, тому що насправді нічого сказати не можна. Якщо ви просто хотіли змусити його працювати, ви можете залишити код таким, який він є.
Але що буде, якби ви вирішили зробити це педантичним способом і насправді вирішили проблему з моделями дизайну так, як ви цього хотіли?
Ви можете переглядати такий процес:
При розробці коду OO, більшість if
s, які знаходяться в коді, не повинні бути там. Звичайно, якщо ви хочете порівняти два скалярні типи, наприклад, int
s або float
s, у вас, швидше за все, є if
, але якщо ви хочете змінити процедури, засновані на конфігурації, ви можете використовувати поліморфізм для досягнення того, що ви хочете, переміщення рішень ( if
s) від вашої ділової логіки до місця, де об'єкти інстанційні - до заводів .
На сьогоднішній день ваш процес може пройти 4 окремі шляхи:
data
не шифрується і не стискається (нічого не дзвоніть, повертайтеся data
)
data
стискається (дзвоніть compress(data)
і повертайте його)
data
зашифровано (зателефонуйте encrypt(data)
та поверніть його)
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: Була бурхлива дискусія щодо першої версії мого рішення. Дискусія здебільшого викликана мною, за що я вибачаюся.
Я вирішив відредагувати відповідь таким чином, що це один із способів подивитися на рішення, але не єдиний. Я також видалив декоративну частину, де я мав на увазі фасад, який, врешті-решт, вирішив повністю залишити, тому що адаптер є варіацією фасаду.
if
заяви?