Чи порушує "склад над успадкуванням" "сухий принцип"?


36

Наприклад, врахуйте, що у мене є клас для інших класів, який слід продовжити:

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

і деякі підкласи:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

Насправді, підклас не має жодних методів, які можна переосмислити, також я б не входив до HomePage загальним способом, тобто: я б не робив чогось типу:

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

Я просто хочу повторно використовувати сторінку входу. Але згідно з https://stackoverflow.com/a/53354 , я віддаю перевагу композиції тут, оскільки мені не потрібен інтерфейс LoginPage, тому я не використовую спадщину тут:

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

але проблема приходить тут, у новій версії, код:

public LoginPage loginPage;

дублікатів, коли додається новий клас. І якщо для LoginPage потрібен сетер і getter, потрібно скопіювати більше кодів:

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

Отже, моє запитання: чи "склад над успадкуванням" порушує "сухий принцип"?


13
Іноді не потрібно ні спадкування, ні складу, тобто, що іноді один клас із кількома об'єктами виконує цю роботу.
Ерік Ейдт

23
Чому ви хочете, щоб усі ваші сторінки були сторінкою для входу? Можливо, просто є сторінка для входу.
Містер Кохез

29
Але, маючи спадок, у вас є дублікат у extends LoginPageвсьому місці. ПЕРЕВІРИТЕ МАТУ!
el.pescado

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

4
Отже, зробіть вашу сторінку, щоб вона могла бути прикрашена, і зробіть LoginPageїї декоратором. Більше не дублювання, просто просте page = new LoginPage(new EditInfoPage())і ви все зробили. Або ви використовуєте принцип відкритого закритого типу, щоб зробити модуль аутентифікації, який можна динамічно додавати на будь-яку сторінку. Існує маса способів боротьби з дублюванням коду, в основному це стосується пошуку нової абстракції. LoginPageшвидше за все, це неправильне ім'я, що ви хочете переконатися, що користувач перевіряє автентифікацію під час перегляду цієї сторінки, при цьому він переспрямовується на LoginPageабо відображається належне повідомлення про помилку, якщо його немає.
Полігном

Відповіді:


46

Почекайте, ви стурбовані тим, що повторити

public LoginPage loginPage;

у двох місцях порушує сухий? За цією логікою

int x;

тепер може існувати лише один об’єкт у всій базі коду. Bleh.

DRY - це добре враховувати, але давай. Крім того

... extends LoginPage

отримує дублювання у вашій альтернативі, тому навіть бути анальним щодо DRY не маю сенсу цього.

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

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

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

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

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

Але так, це багато додаткового набору тексту на клавіатурі. Поки я можу запобігти проблемі йо-йо підстрибування спадкового стеку, коли я читаю код, воно того варте.

Тепер це не те, що я відмовляюся ніколи використовувати спадщину. Одне з моїх улюблених застосувань - давати винятки новим іменам:

public class MyLoginPageWasNull extends NullPointerException{}

int x;може існувати не більше нуля разів у кодовій базі
К. Алан Бейтс


... я знав, що ти збираєшся передзвонити з класом Point Lol. Ніхто не переймається класом Point. Якщо я заходжу у вашу кодову базу і побачу, int a; int b; int x;я натискаю, щоб "ви" видалили.
К. Алан Бейтс

@ K.AlanBates Нічого собі прикро, що ти думаєш, що така поведінка зробить тебе хорошим кодером. Найкращий кодер - це не той, хто може знайти більшість речей про інших. Саме той робить решту кращими.
candied_orange

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

127

Поширене непорозуміння з принципом DRY полягає в тому, що воно якимось чином пов'язане з не повторенням рядків коду. Принцип DRY - "Кожен предмет повинен мати єдине, однозначне, авторитетне представлення в системі" . Йдеться про знання, а не про коди.

LoginPageзнає, як намалювати сторінку для входу в систему. Якби EditInfoPageзнав, як це зробити, то це було б порушенням. У тому числі з LoginPageдопомогою композиції НЕ в жодному разі порушення принципу DRY.

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


27
"Принцип єдиної відповідальності" зловживається набагато більше. "Не повторюй себе", може легко увійти як номер два :-)
gnasher729

28
"DRY" - це зручна абревіатура для "Не повторюй себе", що є фразовою фразою, але не назвою принципу, власне назва принципу - "Раз і тільки один раз", що, в свою чергу, є привабливою назвою за принцип, який потребує параграфа для опису. Важливим є розуміння пари абзаців, не запам’ятовування трьох букв D, R і Y.
Jörg W Mittag,

4
@ gnasher729 Ви знаєте, я фактично згоден з вами, що SRP, мабуть, зловживають набагато більше. Хоча для справедливості, у багатьох випадках я думаю, що їх часто разом зловживають. Моя теорія полягає в тому, що програмістам взагалі не слід довіряти використання абревіатур з "простими іменами".
wasatz

17
ІМХО, це прекрасна відповідь. Однак, якщо чесно: на мій досвід, найчастіша форма порушень DRY викликана програмуванням копію-вставки та просто дублюванням коду. Хтось сказав мені один раз, "коли ви збираєтесь скопіювати і вставити якийсь код, подумайте двічі, якщо дублюючу частину не вдасться витягти в загальну функцію" - що було дуже хорошою порадою ІМХО.
Док Браун

9
Зловживання копіювальною пастою, безумовно, є рушійною силою для порушення DRY, але просто заборона копії-пасти є поганим керівництвом для слідування DRY. Я не відчуваю жодного жаління за наявність двох методів з однаковим кодом, коли ці методи представляють різні знання. Вони виконують два різних обов'язки. Вони просто мають однаковий код сьогодні. Вони повинні вільно змінюватися незалежно. Так, знання, а не код. Добре кажучи. Я написав одну з інших відповідей, але я схиляюся перед цією. +1.
candied_orange

12

Коротка відповідь: так, це так - до незначної, прийнятної міри.

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

Чому це допустимо? Подивіться на методи, які повторюються в сценарії композиції - ці методи виключно делегують виклики в інтерфейс компонента, тому не містять реальної логіки.

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

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

Примітка до вашого прикладу: я не знаю, як виглядають ваш HomePageі ваш EditInfoPage, але якщо вони мають функцію входу, а HomePage(або an EditInfoPage) - це LoginPage , то спадкування може бути правильним інструментом тут. Менш дискусійний приклад, коли композиція стане кращим інструментом у більш очевидний спосіб, ймовірно, зробить все зрозумілішим.

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

  • витягнути частину багаторазового використання LoginPageв власний компонент

  • повторно використовувати цей компонент в HomePageі EditInfoPageтак само , як він використовується в даний час вLoginPage

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

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