Чи переважають конкретні методи кодовим запахом?


31

Чи правда, що переважаючі конкретні методи - це кодовий запах? Тому що я думаю, якщо вам потрібно перекрити конкретні методи:

public class A{
    public void a(){
    }
}

public class B extends A{
    @Override
    public void a(){
    }
}

його можна переписати як

public interface A{
    public void a();
}

public class ConcreteA implements A{
    public void a();
}

public class B implements A{
    public void a(){
    }
}

і якщо B хоче повторно використовувати () в A, його можна переписати як:

public class B implements A{
    public ConcreteA concreteA;
    public void a(){
        concreteA.a();
    }
}

що не потребує успадкування, щоб перекрити метод, це правда?



4
Незважаючи на те, що (у коментарях нижче), Теластин та Док Браун погоджуються щодо переосмислення, але дають протилежні відповіді так / ні на головне питання, оскільки вони не згодні щодо значення "запаху коду". Таким чином, питання, що стосується дизайну коду, було сильно пов'язане з фрагментом сленгу. І я думаю, що це зайве, оскільки мова йде конкретно про одну техніку проти іншої.
Стів Джессоп

5
Питання не позначене Java, але код видається Java. У C # (або C ++) вам доведеться використовувати virtualключове слово, щоб підтримувати зміни. Отже, питання робиться більш (або менш) неоднозначним особливостями мови.
Ерік Ейдт

1
Схоже, майже кожну функцію мови програмування неминуче після досить років класифікують як "запах коду".
Брендон

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

Відповіді:


34

Ні, це не кодовий запах.

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

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

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

Звичайний випадок методів переосмислення - це реалізація за замовчуванням у базовому класі, яка може бути налаштована або оптимізована в підкласі (особливо в Java 8 із появою методів за замовчуванням в інтерфейсах).

class A {
    public String getDescription(Element e) {
        // return default description for element
    }
}

class B extends A {
    public String getDescription(Element e) {
        // return customized description for element
    }
}

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

interface DescriptionProvider {
    String getDescription(Element e);
}

class A {
    private DescriptionProvider provider=new DefaultDescriptionProvider();

    public final String getDescription(Element e) {
       return provider.getDescription(e);
    }

    public final void setDescriptionProvider(@NotNull DescriptionProvider provider) {
        this.provider=provider;
    }
}

class B extends A {
    public B() {
        setDescriptionProvider(new CustomDescriptionProvider());
    }
}

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

У вашому останньому прикладі не слід Aреалізувати DescriptionProvider?
Виявлено

@Spotted: A міг би реалізувати DescriptionProviderі, таким чином, бути постачальником опису за замовчуванням. Але ідея тут полягає в розділенні проблем: Aпотрібен опис (деякі інші класи теж можуть), тоді як інші реалізації надають їх.
Пітер Уолсер

2
Гаразд, це більше схоже на шаблон стратегії .
Виявлено

@Spotted: ви маєте рацію, приклад ілюструє шаблон стратегії, а не шаблон декоратора (декоратор додав би поведінці до компонента). Я виправлю свою відповідь, дякую.
Пітер Уолсер

25

Чи правда, що переважаючі конкретні методи - це кодовий запах?

Так, загалом, переважаючі конкретні методи - це кодовий запах.

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

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

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


29
Ваше пояснення чудово, але я приходжу точно до протилежного висновку: "ні, перекриття конкретних методів взагалі не є кодовим запахом" - це лише кодовий запах, коли один переосмислює методи, які не мали наміру перекрити. ;-)
Док Браун

2
@DocBrown Рівно! Я також бачу, що пояснення (і особливо висновок) суперечать початковій заяві.
Терсосаврос

1
@Spotted: Найпростіший контрприклад - це Numberклас із increment()методом, який встановлює значення наступного дійсного значення. Якщо ви підкласируєте його до місця, EvenNumberде increment()додається два замість одного (а сеттер забезпечує рівність), перша пропозиція ОП вимагає дублювання реалізацій кожного методу в класі, а його другий вимагає написання обгорткових методів для виклику методів у члені. Частина мети успадкування полягає в тому, щоб дитячі класи мали зрозуміти, чим вони відрізняються від батьків, лише надаючи ті методи, які мають бути різними.
Blrfl

5
@DocBrown: те, що запах коду не означає, що щось обов'язково погано, лише це можливо .
mikołak

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

7

якщо вам потрібно перекрити конкретні методи [...], це можна переписати як

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

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


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

@Spotted, існує багато випадків, коли реалізація за замовчуванням може бути надана, і кожна спеціалізація потребує лише перекриття деяких з них, або для розширення функціональності, або для ефективності. Надзвичайний приклад - відвідувачі. Користувач знає, як методи були розроблені з документації; воно повинно бути там, щоб пояснити, що очікується від перекриття в будь-якому випадку.
Ян Худек

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

@Spotted, 1) якщо його треба перекрити, воно повинно бути abstract; але є багато випадків, коли цього не повинно бути. 2) якщо його не слід перекривати, він повинен бути final(невіртуальний). Але все ще існує багато випадків, коли методи можуть бути відмінені. 3) якщо розробник нижче не читає документи, вони збираються стріляти собі в ногу, але вони зроблять це незалежно від того, які заходи ви застосували.
Ян Худек

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

3

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

Це (очевидно) дуже відрізняється від царини статично типових мов, таких як Java, C ++ тощо. Так само, як і природа Сильного введення тексту , як використовується як у Java & C ++, так і у C # та інших.


Я здогадуюсь, що ви походите з фону Java, де методи повинні бути явно позначені як остаточні, якщо дизайнер контрактів має намір їх не перекрити. Однак у мовах, таких як C #, за замовчуванням є навпаки. Методи (без оздоблювальних робіт , спеціальні ключові слова) « запечатані » (C # 's версія final), і повинні бути явно оголошені , virtualякщо вони дозволені бути перевизначені.

Якщо API або бібліотека були розроблені на цих мовах конкретним методом, який можна перезаписати, тоді він повинен (принаймні в деяких випадках) мати сенс для його відміни. Отже, це не запах коду, щоб замінити незапечатаний / віртуальний / не finalконкретний метод.     (Це, звичайно, передбачає, що дизайнери API дотримуються конвенцій і або позначають як virtual(C #), або маркують як final(Java) відповідні методи для того, що вони розробляють.)


Дивись також


Чи є два класи, що мають один і той же підпис методу, для того, щоб вони були сумісними з типом, чи необхідно також, щоб методи мали однакову семантику, щоб вони були заміненими для певного базового класу?
Уейн Конрад

2

Так, це тому, що це може призвести до виклику супер анти-моделі. Він також може денатурировать початкову мету методу, ввести побічні ефекти (=> помилки) та зробити тести невдалими. Чи використовуєте ви клас, одиничні тести якого червоного кольору (невдалі)? Я не буду.

Я піду ще далі, кажу, що початковий запах коду - це коли ні метод, abstractні final. Тому що це дозволяє змінювати (переосмислюючи) поведінку побудованої сутності (цю поведінку можна втратити або змінити). З abstractі finalнаміри однозначні:

  • Анотація: ви повинні надати поведінку
  • Фінал: вам не дозволяється змінювати поведінку

Найбезпечніший спосіб додати поведінку до наявного класу - це те, що показує ваш останній приклад: використання шаблону декоратора.

public interface A{
    void a();
}

public final class ConcreteA implements A{
    public void a() {
        ...
    }
}

public final class B implements A{
    private final ConcreteA concreteA;
    public void a(){
        //Additionnal behaviour
        concreteA.a();
    }
}

9
Цей чорно-білий підхід насправді не так корисний. Подумайте про те, як Object.toString()у Java ... Якби це було abstract, багато речей за замовчуванням не працювали б, а код навіть не збирався б, коли хтось не перекрив toString()метод! Якби це було final, все було б ще гірше - відсутність можливості дозволити об'єкти перетворюватися на рядок, за винятком способу Java. Оскільки у відповіді @Peter Walser йдеться про важливе значення , реалізація за замовчуванням (наприклад, Object.toString()) є важливою. Особливо в методах Java 8 за замовчуванням.
Терсосаврос

@Tersosauros Ви маєте рацію, якби це toString()було, abstractабо finalце буде біль. Моя особиста думка полягає в тому, що цей метод порушений, і було б краще його взагалі не мати (як рівний і хеш-код ). Початкова мета за замовчуванням Java - дозволити еволюцію API з ретросумісністю.
Виявлено

Слово "ретросумісність" містить дуже багато складів.
Крейг

@Spotted Я дуже розчарований загальним прийняттям конкретної спадщини на цьому веб-сайті, я очікував кращого: / Ваша відповідь має мати ще багато результатів
TheCatWhisperer

1
@TheCatWhisperer Я згоден, але, з іншого боку, мені знадобилося стільки часу, щоб дізнатися про найтонші переваги використання декору над конкретним успадкуванням (адже це стало зрозуміло, коли я почав писати тести спочатку), що ви ні в чому не можете звинувачувати в цьому. . Найкраще зробити це - спробувати виховати інших, поки вони не відкриють Істину (жарт). :)
Відмічено
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.