Правда ли, что переопределение конкретных методов - это запах кода? Потому что я думаю, что если вам нужно переопределить конкретные методы:
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();
}
}
который не требует наследования для переопределения метода, это правда?
virtual
ключевое слово для поддержки переопределений. Таким образом, проблема становится более (или менее) неоднозначной из-за особенностей языка.final
модификатор существует именно для таких ситуаций. Если поведение метода таково, что он не предназначен для переопределения, пометьте его какfinal
. В противном случае ожидайте, что разработчики могут переопределить его.Ответы:
Нет, это не кодовый запах.
В обязанности каждого класса входит тщательное рассмотрение вопроса о том, подходит ли подкласс и какие методы могут быть переопределены .
Класс может определять себя или любой метод как конечный или может накладывать ограничения (модификаторы видимости, доступные конструкторы) на то, как и где он находится в подклассе.
Обычным случаем переопределения методов является реализация по умолчанию в базовом классе, которая может быть настроена или оптимизирована в подклассе (особенно в Java 8 с появлением методов по умолчанию в интерфейсах).
Альтернатива для переопределения поведения является шаблоном стратегии , где поведение абстрагируются как интерфейс, и реализация может быть установлена в классе.
источник
A
реализовыватьDescriptionProvider
?A
может реализовыватьDescriptionProvider
и, следовательно, быть поставщиком описания по умолчанию. Но идея здесь заключается в разделении интересов:A
необходимо описание (некоторые другие классы тоже могут), в то время как другие реализации предоставляют их.Да, в общем, переопределение конкретных методов - это запах кода.
Поскольку с базовым методом связано поведение, которое разработчики обычно уважают, его изменение приведет к ошибкам, когда ваша реализация сделает что-то другое. Хуже того, если они изменят поведение, ваша ранее правильная реализация может усугубить проблему. И вообще, довольно сложно соблюдать принцип подстановки Лискова для нетривиальных конкретных методов.
Теперь есть случаи, когда это можно сделать, и это хорошо. Полу-тривиальные методы могут быть безопасно переопределены. Базовые методы, которые существуют только как «нормальный» тип поведения, предназначенный для переопределения, имеют хорошее применение.
Следовательно, мне это кажется запахом - иногда хорошим, обычно плохим, посмотрите, чтобы убедиться.
источник
Number
класс сincrement()
методом, который устанавливает значение в следующее допустимое значение. Если вы подклассифицируете его на то,EvenNumber
гдеincrement()
добавляются два вместо одного (а установщик обеспечивает равномерность), первое предложение OP требует дублирующих реализаций каждого метода в классе, а его второе требует написания методов-оберток для вызова методов в элементе. Часть цели наследования состоит в том, чтобы детские классы прояснили, чем они отличаются от родителей, предоставляя только те методы, которые должны отличаться.Если это можно переписать таким образом, это означает, что у вас есть контроль над обоими классами. Но затем вы знаете, разработали ли вы класс A таким образом, и если вы это сделали, то это не запах кода.
Если, с другой стороны, у вас нет контроля над базовым классом, вы не можете переписать его таким образом. Таким образом, либо класс был разработан таким образом, чтобы его можно было выводить, и в этом случае просто вперёд, это не запах кода, или это не так, и в этом случае у вас есть два варианта: искать другое решение вообще или продолжать в любом случае , потому что рабочие бьют дизайнерскую чистоту.
источник
abstract
; но есть много случаев, когда это не должно быть. 2) если оно не должно быть переопределено, оно должно бытьfinal
(не виртуальным). Но есть еще много случаев, когда методы могут быть переопределены. 3) если нижестоящий разработчик не читает документы, он собирается выстрелить себе в ногу, но он будет делать это независимо от того, какие меры вы предпримете.Во-первых, позвольте мне отметить, что этот вопрос применим только в определенных системах ввода. Например, в системах структурной типизации и типизации Duck - класс, имеющий просто метод с тем же именем и сигнатурой, будет означать, что класс совместим по типу (по крайней мере, для этого конкретного метода, в случае типизации Duck).
Это (очевидно) очень отличается от области статически типизированных языков, таких как Java, C ++ и т. Д. А также от природы строгой типизации , используемой как в Java и C ++, так и в C # и других.
Я предполагаю, что вы пришли из Java-фона, где методы должны быть явно помечены как окончательные, если разработчик контракта намеревается их не переопределить. Однако в таких языках, как C #, по умолчанию используется обратное. Методы (без каких-либо декораций, специальных ключевых слов) « запечатаны » (версия C #
final
) и должны быть явно объявлены так, какvirtual
если бы им было разрешено переопределять.Если API или библиотека были разработаны на этих языках с конкретным методом, который можно переопределить, то он должен (по крайней мере, в некоторых случаях) иметь смысл для его переопределения. Следовательно, не является запахом кода переопределение незапечатанного / виртуального / не
final
конкретного метода. (Это предполагает, конечно, что разработчики API следуют соглашениям и либо помечают какvirtual
(C #), либо помечают какfinal
(Java) подходящие методы для того, что они проектируют.)Смотрите также
источник
Да, это потому, что это может вызвать супер -паттерн. Он также может денатурировать начальную цель метода, вводить побочные эффекты (=> ошибки) и делать тесты неудачными. Вы бы использовали класс с юнит-тестами красного цвета (с ошибками)? Я не буду
Я пойду еще дальше, сказав, что первоначальный запах кода - это когда метод не является ни тем,
abstract
ни другимfinal
. Потому что это позволяет изменить (переопределив) поведение созданного объекта (это поведение может быть потеряно или изменено). Сabstract
иfinal
намерение однозначно:Самый безопасный способ добавить поведение в существующий класс - это то, что показывает ваш последний пример: использование шаблона декоратора.
источник
Object.toString()
Java ... Если бы это было такabstract
, многие вещи по умолчанию не сработали бы, и код даже не скомпилировался бы, если бы кто-то не переопределилtoString()
метод! Если бы это было такfinal
, все было бы еще хуже - нет возможности разрешать преобразование объектов в строку, за исключением способа Java. Как @Peter Walser ответа переговоры «s о, по умолчанию реализации (напримерObject.toString()
) имеют важное значение. Особенно в Java 8 по умолчанию методы.toString()
былabstract
илиfinal
это было бы боль. Мое личное мнение таково, что этот метод не работает, и было бы лучше вообще его не иметь (например, equals и hashCode ). Первоначальная цель Java по умолчанию состоит в том, чтобы обеспечить развитие API с ретро-совместимостью.