Чи є перевантаження прикладом принципу відкритого / закритого?


12

У Вікіпедії йдеться

"програмні об'єкти (класи, модулі, функції тощо) повинні бути відкритими для розширення, але закритими для модифікації"

Слово функції зловила мої очі, і тепер мені цікаво , якщо можна припустити , що створення перевантаження для методу можна розглядати як приклад відкритого / закритого принципу чи ні?

Дозвольте пояснити приклад. Подумайте, що у вашому сервісному шарі є метод, який використовується майже в 1000 місцях. Метод отримує userId і визначає, чи є він адміністратором чи ні:

bool IsAdmin(userId)

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

public bool IsAdmin(string username)
{
    int userId = UserManager.GetUser(username).Id;
    return IsAdmin(userId);
}

Таким чином, ми розширили нашу функцію, створивши для неї перевантаження (функції повинні бути відкриті для розширення).

Це приклад відкритого / закритого принципу?

Відповіді:


5

Я особисто інтерпретував би висловлення wiki таким чином:

  • для класу: сміливо успадковуйте клас та переосмислюйте чи розширюйте його функціональні можливості, але не рубіть початковий клас, змінюючи тим самим, що він робить.
  • для модуля (можливо, наприклад, бібліотеки): не соромтесь написати новий модуль / бібліотеку, яка обгортає оригінал, об'єднує функції у більш прості у використанні версії або розширює оригінал з додатковими функціями, але не змінюйте оригінальний модуль.
  • для функції (тобто статичної функції, а не методу класу): ваш приклад для мене розумний; повторно використовувати оригінальну функцію IsAdmin (int) всередині нового IsAdmin (рядок). оригінал не змінюється, новий функція розширює свою функціональність.

Однак, якщо ваш код використовує клас 'cUserInfo', в якому перебуває IsAdmin (int), ви, по суті, порушуєте правило і змінюєте клас. правило, на жаль, зберігатиметься лише в тому випадку, якщо ви створили новий клас cUserWithNameInfo: загальнодоступний клас cUserInfo і помістите там свій перехід IsAdmin (string). Якби я володів базою коду, я б ніколи не підкорявся цьому правилу. Я б сказав, що боллокінг, і просто внесіть запропоновані вами зміни.


3

По-перше, ваш приклад, на жаль, надуманий. Ви ніколи цього не робите в реальному світі, оскільки це викликає непотрібне подвійне отримання. Або, що ще гірше, тому що userid та ім'я користувача в певний момент можуть стати одним типом. Швидше ніж

public bool IsAdmin(int userid)
{
    User user = UserManager.GetUser(userid);
    return user.IsAdmin();
}

public bool IsAdmin(string username)
{
    int userId = UserManager.GetUser(username).Id;
    return IsAdmin(userId);
}

Вам дійсно слід продовжити цей клас.

public bool IsAdmin(int userid)
{
    User user = UserManager.GetUserById(userid);
    return user.IsAdmin();
}

public bool IsUsernameAdmin(string username)
{
    User userId = UserManager.GetUserByName(username);
    return user.IsAdmin();
}

Ви можете додатково рефактор і змінити ім'я першого методу на IsUserIdAdmin, для послідовності, але це не обов'язково. Справа в тому, що, додаючи новий метод і гарантуючи, що код виклику не порушений, ви визнали свій клас розширюваним. Або іншими словами, відкриті для розширення .

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

Як каже дядько Боб (наголос мій):

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


Дякуємо за ваше добре пояснення. Але наявність 1000 поїздок в обидва кінці або одна туди-назад не є головним моментом. Таким чином, давайте забудемо про продуктивність на даний момент. Однак те, що я зробив, це також розширити клас, тому що я додав до нього інший метод, хоч і з тим самим ім'ям, але з різними вхідними параметрами. Але я дуже оцінив вашу цитату від дядька Боба . +1. ;)
Саїд Неаматі

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

2

Модулі, які відповідають принципу відкритого закритого типу, мають два основні ознаки.

  1. Вони "Відкриті для розширення". Це означає, що поведінку модуля можна продовжити. Що ми можемо змусити модуль вести себе по-новому і по-різному, коли змінюються вимоги програми або задовольняють потреби нових програм.
  2. Вони "закриті для зміни". Вихідний код такого модуля є непорушним. Ніхто не може вносити зміни до вихідного коду.
  1. Перевантажуючи свій метод, ви розширюєте функціональність існуючого модуля, таким чином задовольняючи нові потреби вашої програми

  2. Ви не вносите жодних змін до існуючого методу, тому ви не порушуєте другого правила.

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

Посилання: http://www.objectmentor.com/resources/articles/ocp.pdf


1

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

Але скажімо, у вас була функція під назвою BigContrivedMethod(int1, int2, string1). BigContrivedMethodробить три речі: річ1, річ2 і річ3. На даний момент повторне використання BCM, ймовірно, важке, оскільки це робить занадто багато. Переробляючи його (якщо можливо) на ContrivedFunction1(int), ContrivedFunction2(int)і ContrivedFunction3(string)ви отримуєте три менші, більш цілеспрямовані методи, які ви можете легше поєднувати.

І це є ключовим для OCP щодо методів / функцій: складу. Ви "розширюєте" функції, викликаючи їх з інших функцій.

Пам'ятайте, що OCP є частиною 5 інших принципів, інструкцій SOLID. Цей перший є ключовим, Єдина відповідальність. Якщо все у вашій кодовій базі робило лише одну конкретну річ, яку вона мала робити, вам ніколи не потрібно буде змінювати код. Вам потрібно буде лише додати новий код або об'єднати старий код разом новими способами. Оскільки реальний код рідко відповідає цьому керівництву, вам часто доводиться змінювати його, щоб отримати SRP, перш ніж отримати OCP.


Я думаю, ви тут говорите про принципа єдиної відповідальності, а не про ОГП.
Саїд Неаматі

1
Я кажу, що реально не можна мати OCP без SRP. Код, який робить занадто багато, неможливо розширити, його неможливо повторно використовувати. SOLID майже написаний в порядку важливості, причому кожен принцип покладається на ті, що були перед ним, щоб здійснити.
CodexArcanum

Цю відповідь слід більше підтримувати.
Ашиш Гупта

0

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

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

Коли ви стикаєтеся з чимось, що вам потрібно змінити, замість того, щоб просто щось змінити, щоб дозволити нову поведінку, ви зрозумієте, в чому полягає зміна, реструктуруйте свою програму, не змінюючи її поведінку, щоб потім ви могли змінити цю поведінку, написавши НОВИЙ код .

Отже, як це стосується вашої справи:

Якщо ви додаєте нові функції до свого класу, то ваш клас не відкритий / закритий, але клієнти функції, яку ви перевантажуєте, є.

Якщо ви просто додаєте нові функції, які працюють на ваш клас, то і він, і клієнти вашої функції відкриті / закриті.

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