Рассмотрим следующий дизайн
public class Person
{
public virtual string Name { get; }
public Person (string name)
{
this.Name = name;
}
}
public class Karl : Person
{
public override string Name
{
get
{
return "Karl";
}
}
}
public class John : Person
{
public override string Name
{
get
{
return "John";
}
}
}
Вы думаете, что-то здесь не так? Для меня классы Карла и Джона должны быть просто экземплярами, а не классами, поскольку они точно такие же, как:
Person karl = new Person("Karl");
Person john = new Person("John");
Зачем мне создавать новые классы, когда экземпляров достаточно? Классы ничего не добавляют к экземпляру.
programming-practices
inheritance
class-design
anti-patterns
Игнасио Солер Гарсия
источник
источник
Ответы:
Нет необходимости иметь конкретные подклассы для каждого человека.
Вы правы, это должны быть примеры.
Цель подклассов: расширить родительские классы
Подклассы используются для расширения функциональности, предоставляемой родительским классом. Например, вы можете иметь:
Родительский класс,
Battery
который можетPower()
что-то и может иметьVoltage
свойство,И подкласс
RechargeableBattery
, который может наследоватьPower()
иVoltage
, но также может бытьRecharge()
d.Обратите внимание, что вы можете передать экземпляр
RechargeableBattery
класса в качестве параметра любому методу, который принимаетBattery
в качестве аргумента. Это называется принцип замещения Лискова , один из пяти принципов SOLID. Точно так же, в реальной жизни, если мой MP3-плеер принимает две батарейки АА, я могу заменить их двумя перезаряжаемыми батарейками АА.Обратите внимание, что иногда трудно определить, нужно ли вам использовать поле или подкласс для представления различия между чем-либо. Например, если вам приходится работать с батареями типа AA, AAA и 9-вольтовым, вы бы создали три подкласса или использовали enum? «Замените подкласс на поля» в статье « Рефакторинг » Мартина Фаулера, стр. 232, может дать вам некоторые идеи и способы перехода от одного к другому.
В вашем примере,
Karl
иJohn
ничего не распространяются, они не предоставляют каких - либо дополнительную ценность: вы можете иметь точно такую же функциональность , используяPerson
класс непосредственно. Наличие большего количества строк кода без дополнительного значения никогда не бывает хорошо.Пример бизнес-кейса
Что может быть деловым случаем, когда имеет смысл создать подкласс для конкретного человека?
Допустим, мы создаем приложение, которое управляет людьми, работающими в компании. Приложение также управляет разрешениями, поэтому Хелен, бухгалтер, не может получить доступ к репозиторию SVN, но Томас и Мэри, два программиста, не могут получить доступ к документам, связанным с бухгалтерским учетом.
Джимми, большой босс (основатель и генеральный директор компании), имеет особые привилегии, которых никто не имеет. Он может, например, закрыть всю систему или уволить человека. Вы поняли идею.
Самая плохая модель для такого применения - иметь такие классы, как:
потому что дублирование кода возникнет очень быстро. Даже в самом простом примере с четырьмя сотрудниками вы будете дублировать код между классами Томаса и Мэри. Это подтолкнет вас к созданию общего родительского класса
Programmer
. Поскольку у вас также может быть несколько бухгалтеров, вы, вероятно, создадите иAccountant
класс.Теперь вы замечаете, что наличие класса
Helen
не очень полезно, а также хранениеThomas
иMary
: большая часть вашего кода работает на верхнем уровне - на уровне бухгалтеров, программистов и Джимми. Сервер SVN не заботится о том, должен ли Томас или Мэри получать доступ к журналу, ему нужно только знать, программист это или бухгалтер.В итоге вы удалите классы, которые не используете:
«Но я могу оставить Джимми как есть, так как всегда будет только один генеральный директор, один большой босс - Джимми», - думаете вы. Более того, Джимми часто используется в вашем коде, который на самом деле выглядит примерно так, а не так, как в предыдущем примере:
Проблема с этим подходом заключается в том, что Джимми все еще может сбить автобус, и будет новый генеральный директор. Или совет директоров может решить, что Мэри настолько хороша, что должна стать новым генеральным директором, а Джимми будет понижен до должности продавца, поэтому теперь вам нужно пройтись по всему вашему коду и изменить все.
источник
Глупо использовать такую структуру классов только для изменения деталей, которые, очевидно, должны быть настраиваемыми полями в экземпляре. Но это относится к вашему примеру.
Создание разных
Person
классов почти наверняка является плохой идеей - вам придется менять свою программу и перекомпилировать каждый раз, когда новый человек входит в ваш домен. Однако это не означает, что наследование от существующего класса, так что где-то возвращается другая строка, иногда бесполезно.Разница в том, как вы ожидаете, что сущности, представленные этим классом, будут различаться. Предполагается, что люди почти всегда приходят и уходят, и ожидается, что почти каждое серьезное приложение, имеющее дело с пользователями, сможет добавлять и удалять пользователей во время выполнения. Но если вы моделируете такие вещи, как разные алгоритмы шифрования, поддержка нового алгоритма, вероятно, в любом случае является серьезным изменением, и имеет смысл изобрести новый класс,
myName()
метод которого возвращает другую строку (иperform()
метод которого предположительно делает что-то другое).источник
В большинстве случаев вы этого не сделаете. Ваш пример действительно хороший случай, когда это поведение не добавляет реальной ценности.
Это также нарушает принцип Open Closed , поскольку подклассы в основном не являются расширением, а изменяют внутреннюю работу родителя. Более того, открытый конструктор родителя теперь раздражает в подклассах, и API, таким образом, стал менее понятным.
Однако иногда, если у вас когда-либо будет одна или две специальные конфигурации, которые часто используются во всем коде, иногда удобнее и меньше времени просто создавать подкласс для родительского объекта со сложным конструктором. В таких особых случаях я не вижу ничего плохого в таком подходе. Давайте назовем это конструктор карри J / K
источник
Если это степень практики, то я согласен, что это плохая практика.
Если Джон и Карл ведут себя по-разному, все немного меняется. Возможно, у этого
person
есть метод, позволяющийcleanRoom(Room room)
выяснить, что Джон - отличный сосед по комнате и очень эффективно убирает, а Карл - нет, и он не очень часто убирает в комнате.В этом случае имело бы смысл иметь их в качестве собственного подкласса с определенным поведением. Есть лучшие способы достичь того же (например,
CleaningBehavior
класс), но по крайней мере этот способ не является ужасным нарушением принципов ОО.источник
В некоторых случаях вы знаете, что будет только определенное количество экземпляров, и тогда было бы нормально (хотя и на мой взгляд, некрасиво) использовать этот подход. Лучше использовать enum, если это позволяет язык.
Пример Java:
источник
Многие уже ответили. Думаю, я бы дал свою собственную точку зрения.
Когда-то я работал над приложением (и до сих пор занимаюсь), которое создает музыку.
Приложение было абстрактный
Scale
класс с несколькими подклассов:CMajor
,DMinor
и т.д.Scale
смотрел что - то вроде этого:Музыкальные генераторы работали с конкретным
Scale
экземпляром для создания музыки. Пользователь будет выбирать масштаб из списка, из которого будет генерироваться музыка.Однажды мне пришла в голову классная идея: почему бы не позволить пользователю создавать свои собственные весы? Пользователь будет выбирать заметки из списка, нажимать кнопку, и новый список будет добавлен в список доступных шкал.
Но я не смог этого сделать. Это было потому, что все шкалы уже установлены во время компиляции - так как они выражены как классы. Затем меня поразило:
Часто интуитивно мыслить в терминах «суперклассов и подклассов». Почти все , что можно выразить через эту систему: суперкласс
Person
и подклассыJohn
иMary
; суперклассCar
и подклассыVolvo
иMazda
; суперклассMissile
и подклассыSpeedRocked
,LandMine
иTrippleExplodingThingy
.Это очень естественно думать, особенно для человека относительно новичка в ОО.
Но мы всегда должны помнить, что классы - это шаблоны , а объекты - это содержимое, добавляемое в эти шаблоны . Вы можете добавлять любой контент в шаблон, создавая бесчисленные возможности.
Это не работа подкласса, чтобы заполнить шаблон. Это работа объекта. Работа подкласса заключается в добавлении фактической функциональности или расширении шаблона .
И именно поэтому я должен был создать конкретный
Scale
класс сNote[]
полем и позволить объектам заполнять этот шаблон ; возможно через конструктор или что-то. И в конце концов я так и сделал.Каждый раз, когда вы разрабатываете шаблон в классе (например, пустой
Note[]
элемент, который необходимо заполнить, илиString name
поле, которому необходимо присвоить значение), помните, что заполнение шаблона - это задача объектов этого класса ( или, возможно, те, кто создает эти объекты). Подклассы предназначены для добавления функциональности, а не для заполнения шаблонов.У вас может возникнуть соблазн создать систему «суперкласса
Person
, подклассовJohn
иMary
», как вы это делали, потому что вам нравится формальность, которую вы получаете.Таким образом, вы можете просто сказать
Person p = new Mary()
, вместоPerson p = new Person("Mary", 57, Sex.FEMALE)
. Это делает вещи более организованными и более структурированными. Но, как мы уже говорили, создание нового класса для каждой комбинации данных не очень хороший подход, так как он раздувает код даром и ограничивает вас с точки зрения возможностей времени выполнения.Итак, вот решение: используйте базовую фабрику, возможно, даже статическую. Вот так:
Таким образом, вы можете легко использовать «предустановки» и «идти с программой», например, так:,
Person mary = PersonFactory.createMary()
но вы также оставляете за собой право динамически создавать новых людей, например, в случае, если вы хотите, чтобы пользователь мог это делать , Например:Или даже лучше: сделать что-то вроде этого:
Я увлекся. Я думаю, вы поняли идею.
Подклассы не предназначены для заполнения шаблонов, установленных их суперклассами. Подклассы предназначены для добавления функциональности . Объект предназначен для заполнения шаблонов, вот для чего он.
Вы не должны создавать новый класс для каждой возможной комбинации данных. (Точно так же, как я не должен был создавать новый
Scale
подкласс для каждой возможной комбинацииNote
s).Это руководство: всякий раз, когда вы создаете новый подкласс, подумайте, добавляет ли он какие-либо новые функциональные возможности, которых нет в суперклассе. Если ответ на этот вопрос «нет», то вы, возможно, пытаетесь «заполнить шаблон» суперкласса, и в этом случае просто создайте объект. (И, возможно, Фабрика с «пресетами», чтобы облегчить жизнь).
Надеюсь, это поможет.
источник
Это исключение из «должно быть экземпляров» здесь - хотя и несколько экстремальное.
Если вы хотите, чтобы компилятор принудительно установил права доступа к методам на основе того, является ли это Карлом или Джоном (в этом примере), а не запрашивал реализацию метода, чтобы проверить, какой экземпляр был передан, вы можете использовать другие классы. Применяя различные классы вместо создания экземпляров, вы делаете возможным проверку различий между «Карлом» и «Джоном» во время компиляции, а не во время выполнения, и это может быть полезно, особенно в контексте безопасности, где компилятору можно доверять больше, чем во время выполнения проверка кода (например) внешних библиотек.
источник
Дублировать код считается плохой практикой .
Это по крайней мере частично, потому что строки кодов и, следовательно, размер кодовой базы соотносятся с затратами на обслуживание и дефектами .
Вот соответствующая логика здравого смысла для меня на эту конкретную тему:
1) Если бы мне нужно было это изменить, сколько мест мне нужно посетить? Чем меньше, тем лучше.
2) Есть ли причина, почему это делается таким образом? В вашем примере - не может быть - но в некоторых примерах может быть какая-то причина для этого. Один из них, который я могу придумать, находится в некоторых фреймворках, таких как ранние Grails, где наследование не обязательно хорошо сочеталось с некоторыми плагинами для GORM. По пальцам пересчитать.
3) Это чище и легче понять таким образом? Опять же, это не в вашем примере - но есть некоторые шаблоны наследования, которые могут быть более натянутыми, когда отдельные классы на самом деле проще поддерживать (некоторые виды использования шаблонов команд или стратегий приходят на ум).
источник
Существует вариант использования: формирование ссылки на функцию или метод как обычный объект.
Вы можете увидеть это в коде Java GUI много:
Компилятор поможет вам, создав новый анонимный подкласс.
источник