Я нашел дерево наследования в нашей (довольно большой) базе кода, которая выглядит примерно так:
public class NamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class OrderDateInfo : NamedEntity { }
Из того, что я мог бы собрать, это прежде всего используется, чтобы связать вещи на переднем конце.
Для меня это имеет смысл, так как дает конкретное имя классу, а не полагается на общее NamedEntity
. С другой стороны, существует ряд таких классов, которые просто не имеют дополнительных свойств.
Есть ли недостатки этого подхода?
object-oriented-design
inheritance
Robotron
источник
источник
OrderDateInfo
s от тех, которые относятся к другимNamedEntity
sIdentifier
класс, не имеющий ничего общего,NamedEntity
но требующийId
иName
свойства, и свойства, вы бы не использовалиNamedEntity
вместо этого. Контекст и правильное использование класса - это больше, чем просто свойства и методы, которые он содержит.Ответы:
Подход неплох, но есть лучшие решения. Короче говоря, интерфейс был бы намного лучшим решением для этого. Основная причина, по которой интерфейсы и наследование здесь разные, заключается в том, что вы можете наследовать только от одного класса, но вы можете реализовать много интерфейсов .
Например, предположим, что у вас есть именованные сущности и проверенные сущности. У вас есть несколько объектов:
One
не является проверяемой организацией или именованной организацией. Это просто:Two
является именованным объектом, но не объектом аудита. По сути, это то, что у вас есть сейчас:Three
является как именованной, так и проверенной записью. Здесь вы столкнетесь с проблемой. Вы можете создатьAuditedEntity
базовый класс, но вы не можете сделатьThree
унаследуют какAuditedEntity
иNamedEntity
:Тем не менее, вы можете подумать об обходном пути, получив
AuditedEntity
наследование отNamedEntity
. Это умный взлом, чтобы гарантировать, что каждый класс должен наследовать (напрямую) только от одного другого класса.Это все еще работает. Но то, что вы сделали здесь, гласит, что каждая проверяемая сущность по своей сути также является именованной сущностью . Что подводит меня к моему последнему примеру.
Four
является объектом аудита, но не именованным объектом. Но вы не можете позволитьFour
наследовать от,AuditedEntity
как вы бы тогда сделали этоNamedEntity
из-за наследования между AuditedEntityand
NamedEntity`.Используя наследование, невозможно заставить и то,
Three
и другоеFour
работать, если только вы не начнете дублировать классы (что открывает целый новый набор проблем).Используя интерфейсы, этого легко достичь:
Единственным небольшим недостатком здесь является то, что вам все еще нужно реализовать интерфейс. Но вы получаете все преимущества наличия общего многократно используемого типа, без каких-либо недостатков, возникающих при необходимости вариации нескольких общих типов для данного объекта.
Но ваш полиморфизм остается нетронутым:
Это вариант шаблона интерфейса маркера , где вы реализуете пустой интерфейс исключительно для того, чтобы иметь возможность использовать тип интерфейса для проверки, помечен ли данный класс этим интерфейсом.
Вы используете унаследованные классы вместо реализованных интерфейсов, но цель та же, поэтому я буду называть это «помеченным классом».
На первый взгляд, нет ничего плохого в маркерных интерфейсах / классах. Они синтаксически и технически действительны, и при их использовании нет никаких недостатков при условии, что маркер универсально верен (во время компиляции) и не является условным .
Именно так вы должны различать разные исключения, даже если эти исключения на самом деле не имеют каких-либо дополнительных свойств / методов по сравнению с базовым методом.
Так что в этом нет ничего плохого, но я бы посоветовал использовать это осторожно, убедившись, что вы не просто пытаетесь скрыть существующую архитектурную ошибку с плохо разработанным полиморфизмом.
источник
three
в вашем примере ловушек неиспользования интерфейсов действительно допустим в C ++, например, на самом деле это работает правильно для всех четырех примеров. На самом деле все это кажется ограничением C # как языка, а не предостерегающим рассказом о неиспользовании наследования, когда вам следует использовать интерфейсы.Это то, что я использую, чтобы предотвратить использование полиморфизма.
Скажем, у вас есть 15 различных классов, которые имеют
NamedEntity
базовый класс где-то в их цепочке наследования, и вы пишете новый метод, который применим только кOrderDateInfo
.Вы могли бы просто написать подпись как
void MyMethodThatShouldOnlyTakeOrderDateInfos(NamedEntity foo)
И надеяться и молиться никто не злоупотребляет системой типов, чтобы засунуть
FooBazNamedEntity
туда.Или вы могли бы просто написать
void MyMethod(OrderDateInfo foo)
. Теперь это обеспечивается компилятором. Просто, элегантно и не полагается на людей, не совершающих ошибок.Кроме того, как отметил @candied_orange , исключения являются отличным примером этого. Очень редко (и я имею в виду очень, очень, очень редко) вы когда-нибудь хотите поймать все с помощью
catch (Exception e)
. Скорее всего, вы хотите пойматьSqlException
илиFileNotFoundException
или или пользовательское исключение для вашего приложения. Эти классы часто не предоставляют больше данных или функциональных возможностей, чем базовыйException
класс, но они позволяют вам различать то, что они представляют, без необходимости проверять их и проверять поле типа или искать конкретный текст.В целом, это хитрость, чтобы заставить систему типов позволить вам использовать более узкий набор типов, чем если бы вы использовали базовый класс. Я имею в виду, что вы можете определить все свои переменные и аргументы как имеющие тип
Object
, но это только усложнит вашу работу, не так ли?источник
NamedEntity
это, напримерEntityName
, в так, чтобы композиция имела смысл при описании.Это мое любимое использование наследства. Я использую его в основном для исключений, которые могли бы использовать более точные имена
Обычная проблема наследования, приводящая к длинным цепям и вызывающая проблему йо-йо, здесь не применима, так как нет ничего, что могло бы мотивировать вас на цепочку.
источник
ИМХО дизайн класса неправильный. Должен быть.
OrderDateInfo
Имеет ли AName
более естественное отношение и создает два простых для понимания класса, которые не вызвали бы первоначальный вопрос.Любой метод , который принят в
NamedEntity
качестве параметра должен быть только заинтересован вId
иName
свойствах, так что любые такие методы должны быть изменены , чтобы принятьEntityName
вместо этого.Единственная техническая причина, по которой я согласился бы с оригинальным дизайном, - это привязка свойств, о которой упоминал ОП. Каркас дерьма не сможет справиться с дополнительным свойством и привязаться к нему
object.Name.Id
. Но если ваша обязательная структура не справляется с этим, у вас есть еще один технический долг, который можно добавить в список.Я согласился бы с ответом @ Flater, но с большим количеством интерфейсов, содержащих свойства, вы в итоге написали много стандартного кода, даже с прекрасными автоматическими свойствами C #. Представьте, что вы делаете это на Java!
источник
Классы раскрывают поведение, а структуры данных - данные.
Я вижу ключевые слова класса, но не вижу никакого поведения. Это означает, что я бы начал рассматривать этот класс как структуру данных. В этом духе я перефразирую ваш вопрос как
Таким образом, вы можете использовать тип данных верхнего уровня. Это позволяет использовать систему типов для обеспечения политики для больших наборов различных структур данных, обеспечивая наличие всех свойств.
Таким образом, вы можете использовать тип данных более низкого уровня. Это позволяет вводить подсказки в систему ввода, чтобы выразить назначение переменной
В приведенной выше иерархии нам удобно указывать имя человека, чтобы люди могли получать и изменять свое имя. Хотя было бы разумно добавить дополнительные свойства в
Person
структуру данных, решаемая нами проблема не требует идеального моделирования Person, поэтому мы не добавили общие свойства, такие какage
и т. Д.Таким образом, использование системы ввода в действие позволяет выразить намерение этого Именованного элемента так, чтобы оно не нарушало обновления (например, документацию) и могло быть легко расширено
age
позднее (если вы обнаружите, что поле действительно нужно позже ).источник