Является ли переопределением конкретных методов запаха кода?

Верно ли, что переопределение конкретных методов - это запах кода? Потому что я думаю, что если вам нужно переопределить конкретные методы:

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 () в A, его можно переписать как:

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

, который не требует наследования для переопределения метода, является ли это истинным?

28 голосов | спросил ggrr 11 AMpMon, 11 Apr 2016 05:08:04 +030008Monday 2016, 05:08:04

5 ответов


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());
    }
}
ответил Peter Walser 11 AMpMon, 11 Apr 2016 11:49:32 +030049Monday 2016, 11:49:32
23
  

Правда ли, что переопределение конкретных методов - это запах кода?

Да, в общем, переопределение конкретных методов - это запах кода.

Поскольку базовый метод связан с ним, что разработчики обычно уважают, изменение, которое приведет к ошибкам, когда ваша реализация делает что-то другое. Хуже того, если они изменят поведение, ваша ранее правильная реализация может усугубить проблему. И вообще, довольно трудно уважать Принцип замены Лискова для нетривиальных конкретных методов.

Теперь есть случаи, когда это можно сделать и это хорошо. Несколько тривиальные методы могут быть преодолены несколько безопасно. Базовые методы, которые существуют только как поведение «нормального по умолчанию», которое означает , чтобы быть перегруженным, имеют некоторые полезные возможности.

Следовательно, это похоже на запах для меня - иногда хороший, обычно плохой, взгляните, чтобы убедиться.

ответил Telastyn 11 AMpMon, 11 Apr 2016 05:21:58 +030021Monday 2016, 05:21:58
7
  

, если вам нужно переопределить конкретные методы [...], его можно переписать как

Если это можно переписать таким образом, это означает, что вы имеете контроль над обоими классами. Но тогда вы знаете, был ли вы сконструирован класс А таким образом, и если бы вы это сделали, это не запах кода.

Если, с другой стороны, у вас нет контроля над базовым классом, вы не можете переписать таким образом. Таким образом, либо класс был разработан так, чтобы быть полученным таким образом, и в этом случае просто идти вперед, это не запах кода, или это не так, и в этом случае у вас есть два варианта: искать другое решение вообще или идти в любом случае , потому что рабочие козыри составляют чистоту дизайна.

ответил Jan Hudec 11 AMpMon, 11 Apr 2016 08:58:33 +030058Monday 2016, 08:58:33
3

Во-первых, позвольте мне просто указать, что этот вопрос only применим в некоторых системах набора текста. Например, в Структурная типизация и Duck typing - класс, просто имеющий метод с тем же именем & подпись будет означать , что этот класс был совместим с типом (по крайней мере, для этого конкретного метода, в случае Duck typing).

Это (очевидно) очень отличается от области статически типизированных языков, таких как Java, C ++ и т. д. Также как характер Сильная типизация , используемая в обеих Java & амп; C ++, а также C # и др.


Я предполагаю, что вы исходите из фона Java, где методы должны явно быть отмечен как окончательный , если конструктор контрактов намерен для них не для переопределения. Тем не менее, в таких языках, как C #, по умолчанию используется обратное. Методы (без каких-либо украшений, специальных ключевых слов) « опечатано » (версия C # final) и должна явно объявлен как virtual , если они разрешены для переопределения.

Если API или библиотека была разработана на этих языках с помощью конкретного метода, который является переопределяемым, то он должен (по крайней мере, в some case (s)) сделать смысл для его переопределения. Таким образом, это не запах кода, чтобы переопределить конкретный метод незапечатанного /виртуального /не final. (Это предполагает, что разработчики API следуют соглашениям и обозначают как virtual (C #) или маркируют как final (Java) соответствующие методы для того, что они разрабатывают.)


См. также

ответил Tersosauros 11 AMpMon, 11 Apr 2016 09:14:16 +030014Monday 2016, 09:14:16
2

Да, это , потому что это может привести к вызов super anti -шаблон. Он также может денатурировать первоначальную цель метода, ввести побочные эффекты (=> ошибки) и сделать тесты неудачными. Используете ли вы класс, какие модульные тесты являются красными (failling)? Я не буду.

Я пойду еще дальше, сказав, что первоначальный запах кода - это когда метод не является 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();
    }
}
ответил Spotted 11 AMpMon, 11 Apr 2016 09:48:56 +030048Monday 2016, 09:48:56

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132