Що це означає, коли можна сказати "Інкапсулювати те, що змінюється"?


25

Один із принципів ООП, на які я натрапив, це: -Екапсулювати те, що змінюється.

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


Дивіться en.wikipedia.org/wiki/Encapsulation_(computer_programming), де це добре пояснюється. Я думаю, що "те, що змінюється", не є правильним, оскільки ви також іноді повинні інкапсулювати константи.
qwerty_so

I don't know how exactly would it contribute to a better designІнкапсуляція деталей стосується нещільного зв'язку між "моделлю" та деталями реалізації. Чим менше прив’язана "модель" до деталей реалізації, тим більш гнучким є рішення. І це полегшує еволюцію. "Абстрагуйтесь від деталей".
Лаїв

@Laiv Отже, "варіюється" стосується того, що розвивається протягом життєвого циклу програмного забезпечення або що змінюється під час виконання вашої програми чи обох?
Харіс Гаурі

2
@HarisGhauri і те й інше. Згрупуйте разом те, що варіюється разом. Виділіть те, що змінюється незалежно. Будьте підозрілі до того, що, на вашу думку, ніколи не зміниться.
candied_orange

1
@laiv мислення "абстрактне" - хороший момент. Для цього можна відчути непосильну силу. У будь-якому одному об'єкті, на який ви покладете відповідальність. Приємна річ у тому, що ви лише повинні ретельно подумати про те, що тут є одне. Коли подробиці решти проблеми є кимось іншим, це полегшує справи.
candied_orange

Відповіді:


30

Ви можете написати код, який виглядає приблизно так:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

або ви можете написати код, який виглядає приблизно так:

pet.speak();

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

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

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

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

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

Розв’яжіть, відокремте та прихойте деталі від решти коду. Не дозволяйте знанням деталей поширюватися по вашій системі, і ви будете дотримуватися "інкапсулювати те, що варіюється" просто чудово.


3
Це справді дивно. "Інкапсулювати те, що змінюється" для мене означає приховати зміни стану, наприклад, ніколи не мати глобальних змінних. Але відповідь теж має сенс, навіть якщо він відчуває більше відповіді на поліморфізм, ніж інкапсуляцію :)
Девід Арно

2
Поліморфізм @DavidArno - це один із способів зробити цю роботу. Я міг би просто заграти, якби структура була домашньою твариною, і тут все буде добре виглядати завдяки інкапсуляції вихованця. Але це було б просто переміщення безладу, а не очищення його.
candied_orange

1
"Інкапсулювати те, що змінюється" для мене означає приховати зміни стану . Ні, ні. Мені подобається коментар CO. Відповідь Деріка Елкіна йде глибше, читайте її не раз. Як сказав @JacquesB "Цей принцип насправді досить глибокий"
radarbob

16

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

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

  • ставка податку з продажу є жорстким кодом буквально скрізь у додатку, де нараховується податок з продажу.

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

  • існує єдиний метод, calculateSalesTax(product)який називається єдиним місцем використання ставки податку з продажу.

  • ставка податку з продажу визначається у файлі конфігурації або в базі даних.

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

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

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

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


2
Податки - хороший приклад. Закони та податки обчислення можуть змінюватися від дня до дня. Якщо ви впроваджуєте систему податкового декларування, ви сильно прив'язані до подібних змін. Також змінюється від одного місцевого до іншого (країни, провінції, ...)
Laiv

"Пі не зміниться" змусив мене сміятися. Це правда, Пі навряд чи зміниться, але припустимо, вам більше не дозволяли користуватися? Якщо у деяких людей свій шлях Пі буде застарілим. Припустимо, що це стає вимогою? Сподіваюся, у вас є щасливий день Тау . Гарна відповідь BTW. Глибоко справді.
candied_orange

14

Обидві нинішні відповіді, здається, лише частково досягли позначки, і вони зосереджуються на прикладах, які затьмарюють основну думку. Це також не (виключно) принцип ООП, а загалом принцип розробки програмного забезпечення.

Річ, яка "варіюється" у цій фразі, - це код. Крістоф наголошує на тому, що зазвичай щось може відрізнятися, тобто ви часто цього передбачаєте . Мета - захистити себе від майбутніх змін у коді. Це тісно пов'язане з програмуванням на інтерфейс . Однак Крістоф невірно обмежувати це "деталями реалізації". Насправді цінність цієї поради часто пояснюється змінами вимог .

Це лише опосередковано пов'язане з інкапсулюючим станом, про що я думаю, Девід Арно думає. Ця порада не завжди (але часто є) передбачає стан інкапсуляції, і ця порада стосується також незмінних об'єктів. Насправді просто називання констант є (дуже базовою) формою інкапсуляції того, що змінюється.

CandiedOrange прямо пов'язує "те, що змінюється", з "деталями". Це лише частково правильно. Я погоджуюся, що будь-який код, який змінюється, є "деталями" в якомусь сенсі, але "деталь" може не змінюватися (якщо ви не визначите "деталі", щоб зробити це тавтологічним). Можуть бути причини для інкапсуляції неоднакових деталей, але цей диктум не один. Грубо кажучи, якщо ви були впевнені, що "собака", "кішка" і "качка" будуть єдиними типами, з якими вам коли-небудь доведеться мати справу, то цей диктум не пропонує реконструювати CandiedOrange.

Прикладаючи приклад CandiedOrange в іншому контексті, припустимо, що у нас є процедурна мова типу C. Якщо у мене є код, який містить:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

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

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

і використання цієї нової процедури замість блоку коду (тобто рефакторинг "методом вилучення"). У цей момент додавання типу "корова" або все, що вимагає лише оновлення speakпроцедури. Звичайно, мовою ОО ви можете замість цього використовувати динамічну розсилку, на яку згадується у відповіді CandiedOrange. Це станеться природно, якщо ви отримаєте доступ petчерез інтерфейс. Усунення умовної логіки за допомогою динамічної диспетчеризації є ортогональним питанням, яке було частиною того, чому я зробив це процедурне передання. Я також хочу наголосити, що для OOP для цього не потрібні особливості. Навіть мовою ОО, інкапсуляція різниць не означає, що потрібно створити новий клас або інтерфейс.

Як більш архетипний приклад (який ближчий, але не зовсім ОО), скажімо, що ми хочемо видалити дублікати зі списку. Скажімо, ми реалізуємо це шляхом ітерації над списком, відслідковуючи елементи, які ми бачили до цього часу, в іншому списку та видаляючи будь-які елементи, які ми бачили. Доцільно припустити, що ми, можливо, захочемо змінити те, як ми слідкуємо за побаченими предметами, принаймні, з причин виконання. Дані щодо інкапсуляції того, що змінюється, підказує, що ми повинні побудувати абстрактний тип даних для представлення набору бачених елементів. Тепер наш алгоритм визначений у порівнянні з цим абстрактним типом даних набору даних, і якщо ми вирішимо перейти на двійкове дерево пошуку, наш алгоритм не потребує змін або догляду. Мовою ОО, ми можемо використовувати клас або інтерфейс для зйомки цього абстрактного типу даних. Мовою на зразок SML / O '

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

Хоча це ортогональне занепокоєння, яке не є частиною "інкапсуляції того, що змінюється", часто природним є абстрагування, тобто параметризація за теперішньою капсульованою логікою. Це, як правило, призводить до більш гнучкого коду і дозволяє змінювати логіку заміною в альтернативній реалізації, а не змінюючи інкапсульовану логіку.


О гірка солодка іронія. Так, це не лише питання ООП. Ви зловили мене, що я дозволяв деталі мовної парадигми забруднювати мою відповідь і справедливо покарав мене за це, "змінюючи" парадигму.
candied_orange

"Навіть на мові ОО, інкапсуляція того, що різниться, не обов'язково означає, що потрібно створити новий клас чи інтерфейс" - важко уявити ситуацію, коли не створення нового класу чи інтерфейсу не порушило б SRP
taurelas

11

"Інкапсулювати те, що варіюється" стосується приховування деталей реалізації, які можуть змінюватися та розвиватися.

Приклад:

Наприклад, припустимо, клас Courseвідстежує, Studentsщо може зареєструвати (). Ви можете реалізувати його за допомогою LinkedListта викрити контейнер, щоб дозволити його повторити:

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

Але це не дуже гарна ідея:

  • По-перше, люди можуть бракувати доброї поведінки і використовувати її як самообслуговування, безпосередньо додаючи студентів до списку, не проходячи метод register ().
  • Але ще більше дратує: це створює залежність "використовуючого коду" від внутрішніх деталей реалізації використовуваного класу. Це може запобігти подальші еволюції класу, наприклад, якщо ви хочете використовувати масив, вектор, карту з номером місця або власну стійку структуру даних.

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

Додаткове читання:

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