При планировании своих программ я часто начинаю с такой цепочки мыслей:
Футбольная команда - это просто список футболистов. Поэтому я должен представить это с:
var football_team = new List<FootballPlayer>();
Порядок в этом списке представляет порядок, в котором игроки перечислены в списке.
Но позже я понимаю, что у команд есть и другие свойства, помимо простого списка игроков, которые должны быть записаны. Например, промежуточные итоги в этом сезоне, текущий бюджет, унифицированные цвета, string
обозначение названия команды и т. Д.
Итак, я думаю:
Хорошо, футбольная команда похожа на список игроков, но, кроме того, у нее есть имя (а
string
) и промежуточный результат (аint
). .NET не предоставляет класс для хранения футбольных команд, поэтому я создам свой собственный класс. Наиболее похожей и актуальной существующей структурой являетсяList<FootballPlayer>
, поэтому я унаследую от нее:class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
Но оказывается, что руководство говорит, что вы не должны наследовать отList<T>
. Я полностью смущен этим руководством в двух отношениях.
Почему бы нет?
Видимо List
как-то оптимизировано для производительности . Как же так? Какие проблемы с производительностью я буду вызывать, если буду расширяться List
? Что именно сломается?
Другая причина, которую я видел, заключается в том, что List
она предоставлена Microsoft, и я не могу ее контролировать, поэтому я не могу изменить ее позже, выставив «публичный API» . Но я изо всех сил пытаюсь понять это. Что такое публичный API и почему меня это должно волновать? Если мой текущий проект не имеет и вряд ли когда-либо будет иметь этот публичный API, могу ли я смело игнорировать это руководство? Если я унаследую от вас List
и выясню, что мне нужен публичный API, какие у меня будут трудности?
Почему это вообще имеет значение? Список есть список. Что может измениться? Что я мог бы хотеть изменить?
И, наконец, если Microsoft не хотела, чтобы я унаследовал List
, почему они не сделали этот класс sealed
?
Что еще я должен использовать?
Очевидно, для пользовательских коллекций Microsoft предоставила Collection
класс, который должен быть расширен вместо List
. Но этот класс очень прост и не имеет много полезных вещей, таких какAddRange
, например. Ответ jvitor83 дает обоснование производительности для этого конкретного метода, но как медленный AddRange
не лучше, чем нет AddRange
?
Наследование от Collection
- это гораздо больше работы, чем наследование List
, и я не вижу никакой выгоды. Конечно, Microsoft не сказала бы мне, чтобы я выполняла дополнительную работу без причины, поэтому я не могу избавиться от ощущения, что я как-то неправильно понимаю, а наследование Collection
на самом деле не является правильным решением для моей проблемы.
Я видел такие предложения, как реализация IList
. Просто нет. Это десятки строк стандартного кода, которые мне ничего не дают.
Наконец, некоторые предлагают обернуть что- List
то в:
class FootballTeam
{
public List<FootballPlayer> Players;
}
Есть две проблемы с этим:
Это делает мой код излишне многословным. Теперь я должен позвонить,
my_team.Players.Count
а не простоmy_team.Count
. К счастью, с помощью C # я могу определить индексаторы, чтобы сделать индексирование прозрачным, и переслать все методы внутреннегоList
... Но это много кода! Что я получу за всю эту работу?Это просто не имеет никакого смысла. У футбольной команды нет «списка» игроков. Это является список игроков. Вы не говорите: «Джон Макфутбаллер присоединился к игрокам SomeTeam». Вы говорите: «Джон присоединился к SomeTeam». Вы не добавляете букву к «символам строки», вы добавляете букву к строке. Вы не добавляете книгу в книги библиотеки, вы добавляете книгу в библиотеку.
Я понимаю, что то, что происходит "под капотом", можно сказать, как "добавление X во внутренний список Y", но это кажется очень нелогичным способом мышления о мире.
Мой вопрос (резюмирован)
Каков правильный способ представления структуры данных на C #, который, «логически» (то есть «человеческому разуму»), представляет собой list
всего things
лишь несколько наворотов?
Является ли наследование от List<T>
всегда неприемлемым? Когда это приемлемо? Почему, почему нет? Что должен учитывать программист, когда решает, наследовать List<T>
или нет?
string
Обязан делать все апobject
может сделать и больше .Ответы:
Здесь есть несколько хороших ответов. Я бы добавил к ним следующие моменты.
Попросите десять человек, не являющихся программистами, которые знакомы с существованием футбола, заполнить пробел:
Кто- нибудь сказал «список футболистов с несколькими наворотами», или все они сказали «спортивная команда», «клуб» или «организация»? Ваше представление о том, что футбольная команда - это особый список игроков, находится в вашем человеческом разуме и только в вашем человеческом разуме.
List<T>
это механизм . Футбольная команда - это бизнес-объект, то есть объект, представляющий некоторую концепцию, которая находится в бизнес-сфере программы. Не смешивайте их! Футбольная команда - это своего рода команда; у него есть список, список - это список игроков . Список не является определенным списком игроков . Список - это список игроков. Так что сделайте свойство под названиемRoster
этоList<Player>
. И сделайте это,ReadOnlyList<Player>
пока вы в этом, если вы не верите, что все, кто знает о футбольной команде, могут удалить игроков из реестра.Неприемлемо для кого? Меня? Нет.
Когда вы создаете механизм, который расширяет
List<T>
механизм .Я строю механизм или бизнес-объект ?
Вы потратили больше времени, набирая свой вопрос, что потребовалось бы вам, чтобы написать методы переадресации для соответствующих участников в
List<T>
пятьдесят раз. Вы явно не боитесь многословия, и мы говорим об очень небольшом количестве кода здесь; это несколько минут работы.ОБНОВИТЬ
Я еще немного подумал, и есть еще одна причина, чтобы не называть футбольную команду списком игроков. На самом деле было бы плохой идеей моделировать футбольную команду как имеющую список игроков. Проблема с командой как / , имеющей список игроков , это то , что у вас есть это снимок команды в момент времени . Я не знаю, каково ваше экономическое обоснование для этого класса, но если бы у меня был класс, который представлял футбольную команду, я бы хотел задать ему такие вопросы, как «сколько игроков Seahawks пропустили игры из-за травм в период с 2003 по 2013 год?» или "У какого игрока из Денвера, который ранее играл за другую команду, был самый большой прирост в годовом исчислении?" или " Пиггеры прошли весь путь в этом году? "
То есть футбольная команда кажется мне хорошо смоделированной как совокупность исторических фактов, таких как, когда игрок был завербован, ранен, ушел в отставку и т. Д. Очевидно, что текущий список игроков является важным фактом, который, вероятно, должен быть передовым. центр, но могут быть и другие интересные вещи, которые вы хотите сделать с этим объектом, которые требуют более исторической перспективы.
источник
IEnumerable<T>
- это последовательность ?Ух ты, в твоем посте целый ряд вопросов и моментов. Большая часть рассуждений, которые вы получаете от Microsoft, точно соответствует действительности. Давайте начнем со всего о
List<T>
List<T>
является оптимизированным. Его основное использование должно использоваться как частный член объекта.class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }
. Теперь это так же просто, как и делатьvar list = new MyList<int, string>();
.IList<T>
если вам нужен какой-либо потребитель, чтобы иметь индексированный список. Это позволит вам позже изменить реализацию внутри класса.Collection<T>
очень общее, потому что это общее понятие ... название говорит само за себя; это просто коллекция. Есть более точные версии , такие какSortedCollection<T>
,ObservableCollection<T>
,ReadOnlyCollection<T>
и т.д. , каждый из которых реализуют ,IList<T>
но неList<T>
.Collection<T>
позволяет переопределять элементы (т. е. добавлять, удалять и т. д.), поскольку они являются виртуальными.List<T>
не.Если бы я писал этот код, класс мог бы выглядеть примерно так:
источник
List<T>
это расширенный внутренний класс, который использует обобщения для упрощения использования и объявленияList<T>
. Если расширение было простоclass FootballRoster : List<FootballPlayer> { }
, тогда да, я мог бы использовать псевдоним вместо этого. Полагаю, стоит отметить, что вы можете добавить дополнительные элементы / функции, но это ограничено, поскольку вы не можете переопределить важные элементыList<T>
.Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden
Теперь , что это хорошая причина , чтобы не наследовать из списка. У других ответов слишком много философии.Предыдущий код означает: кучка парней с улицы играет в футбол, и у них есть имя. Что-то вроде:
Во всяком случае, этот код (из моего ответа)
Значит: это футбольная команда, в которой есть менеджмент, игроки, администраторы и т. Д. Что-то вроде:
Вот как ваша логика представлена в картинках ...
источник
private readonly List<T> _roster = new List<T>(44);
System.Linq
пространство имен.Это классический пример композиции против наследования .
В этом конкретном случае:
Является ли команда списком игроков с добавленным поведением?
или
Является ли команда собственным объектом, который содержит список игроков.
Расширяя список, вы ограничиваете себя несколькими способами:
Вы не можете ограничить доступ (например, запретить людям менять реестр). Вы получаете все методы List независимо от того, нужны они вам или нет.
Что произойдет, если вы хотите иметь списки других вещей, а также. Например, в командах есть тренеры, менеджеры, болельщики, экипировка и т. Д. Некоторые из них вполне могут быть списками сами по себе.
Вы ограничиваете свои варианты для наследования. Например, вы можете создать универсальный объект Team, а затем использовать BaseballTeam, FootballTeam и т. Д., Которые наследуются от этого. Чтобы унаследовать от List, вам нужно сделать наследование от Team, но это означает, что все различные типы команд должны иметь одинаковую реализацию этого списка.
Композиция - включая объект, задающий поведение, которое вы хотите внутри вашего объекта.
Наследование - ваш объект становится экземпляром объекта с желаемым поведением.
Оба имеют свое применение, но это очевидный случай, когда состав предпочтительнее.
источник
Как уже отмечалось, команда игроков - это не список игроков. Эту ошибку делают многие люди повсюду, возможно, на разных уровнях знаний. Часто проблема тонкая, а иногда и очень грубая, как в этом случае. Такие конструкции плохие, потому что они нарушают принцип подстановки Лискова . В Интернете есть много хороших статей, объясняющих эту концепцию, например, http://en.wikipedia.org/wiki/Liskov_substitution_principle
Таким образом, есть два правила, которые должны быть сохранены в отношениях Родитель / Ребенок среди классов:
Другими словами, Родитель является необходимым определением ребенка, а ребенок - достаточным определением Родителя.
Вот способ обдумать свое решение и применить вышеупомянутый принцип, который должен помочь избежать такой ошибки. Следует проверить свою гипотезу, проверив, действительны ли все операции родительского класса для производного класса как структурно, так и семантически.
Как видите, только первая характеристика списка применима к команде. Следовательно, команда - это не список. Список будет подробным описанием того, как вы управляете своей командой, поэтому его следует использовать только для хранения объектов игрока и манипулирования методами класса Team.
На этом этапе я хотел бы отметить, что класс Team, по моему мнению, даже не должен быть реализован с использованием List; он должен быть реализован с использованием структуры данных Set (например, HashSet) в большинстве случаев.
источник
Что если у
FootballTeam
него есть резервная команда вместе с основной командой?Как бы вы смоделировали это?
Отношения явно есть, а не есть .
или
RetiredPlayers
?Как правило, если вы хотите наследовать от коллекции, назовите класс
SomethingCollection
.Ваш
SomethingCollection
семантически имеет смысл? Делайте это только если ваш тип является коллекциейSomething
.В случае, если
FootballTeam
это не звучит правильно. АTeam
больше, чемCollection
. АTeam
может иметь тренеров, тренеров и т. Д., Как указали другие ответы.FootballCollection
звучит как коллекция футбольных мячей или, может быть, коллекция футбольных принадлежностей.TeamCollection
Коллекция команд.FootballPlayerCollection
звучит как набор игроков, который будет правильным именем для класса, который наследуется,List<FootballPlayer>
если вы действительно хотите это сделать.На самом деле
List<FootballPlayer>
это очень хороший тип для общения. Может быть,IList<FootballPlayer>
если вы возвращаете его из метода.В итоге
Спроси себя
Есть ? или имеет ?
X
Y
X
Y
Мои названия классов означают, что они?
источник
DefensivePlayers
,OffensivePlayers
илиOtherPlayers
, возможно , было бы законно полезно иметь тип , который может быть использован код , который ожидает ,List<Player>
но также включены членыDefensivePlayers
,OffsensivePlayers
илиSpecialPlayers
типаIList<DefensivePlayer>
,IList<OffensivePlayer>
иIList<Player>
. Можно использовать отдельный объект для кэширования отдельных списков, но инкапсуляция их в том же объекте, что и основной список, может показаться более чистой [использовать недействительность перечислителя списка ...Дизайн> Реализация
Какие методы и свойства вы выставляете - это дизайнерское решение. Базовый класс, от которого вы наследуете, является деталью реализации. Я чувствую, что стоит вернуться к прежнему.
Объект - это набор данных и поведения.
Итак, ваши первые вопросы должны быть:
Имейте в виду, что наследование подразумевает отношения «isa» (is a), тогда как композиция подразумевает отношение «hasa» (hasa). Выберите правильный вариант для вашей ситуации, принимая во внимание, куда все может пойти по мере развития вашего приложения.
Подумайте о мышлении в интерфейсах, прежде чем думать о конкретных типах, так как некоторым людям легче перевести свой мозг в «режим проектирования» таким образом.
Это не то, что каждый делает сознательно на этом уровне в повседневном программировании. Но если вы размышляете над темой такого рода, вы идете по водам. Осознание этого может быть освобождением.
Учитывайте особенности дизайна
Посмотрите на List <T> и IList <T> в MSDN или Visual Studio. Посмотрите, какие методы и свойства они выставляют. Все ли эти методы выглядят как то, что кто-то хотел бы сделать с FootballTeam по вашему мнению?
Имеет ли для вас смысл FootballTeam.Reverse ()? Выглядит ли footballTeam.ConvertAll <TOutput> () что-то, что вы хотите?
Это не вопрос с подвохом; ответ может быть действительно «да». Если вы реализуете / наследуете List <Player> или IList <Player>, вы застряли с ними; если это идеально подходит для вашей модели, сделайте это.
Если вы решите «да», это имеет смысл, и вы хотите, чтобы ваш объект обрабатывался как набор / список игроков (поведение), и поэтому вы хотите реализовать ICollection или IList, во что бы то ни стало. Номинально:
Если вы хотите, чтобы ваш объект содержал коллекцию / список игроков (данных), и поэтому вы хотите, чтобы коллекция или список были свойством или членом, во что бы то ни стало. Номинально:
Вам может показаться, что вы хотите, чтобы люди могли только перечислять набор игроков, а не считать их, добавлять к ним или удалять их. IEnumerable <Player> - вполне допустимый вариант для рассмотрения.
Вы можете почувствовать, что ни один из этих интерфейсов вообще не полезен в вашей модели. Это менее вероятно (IEnumerable <T> полезен во многих ситуациях), но все же возможно.
Любой, кто пытается сказать вам, что одно из них категорически и категорически неверно в каждом случае, ошибается . Любой, кто пытается сказать вам, что он категорически и окончательно прав в любом случае, ошибается.
Перейти к реализации
Как только вы определились с данными и поведением, вы можете принять решение о реализации. Это включает в себя, какие конкретные классы вы зависите от наследования или композиции.
Это не может быть большим шагом, и люди часто объединяют дизайн и реализацию, так как вполне возможно пробежаться по голове за секунду или две и начать печатать.
Мысленный эксперимент
Искусственный пример: как уже упоминали другие, команда не всегда «просто» коллекция игроков. Поддерживаете ли вы коллекцию матчей для команды? Взаимозаменяема ли команда с клубом, в вашей модели? Если это так, и если ваша команда представляет собой набор игроков, возможно, это также набор сотрудников и / или набор очков. Тогда вы в конечном итоге:
Несмотря на дизайн, на данный момент в C # вы все равно не сможете реализовать все это, унаследовав от List <T>, так как C # «only» поддерживает одиночное наследование. (Если вы пробовали эту малярию в C ++, вы можете считать это хорошей вещью.) Реализация одной коллекции с помощью наследования и одной с помощью композиции может показаться грязной. И такие свойства, как Count, приводят пользователей в замешательство, если вы не реализуете ILIst <Player> .Count и IList <StaffMember> .Count и т. Д. Явно, и тогда они просто болезненны, а не сбивают с толку. Вы можете видеть, куда это идет; интуитивное чувство, размышляя над этим проспектом, вполне может сказать вам, что неправильно идти в этом направлении (и правильно или неправильно, ваши коллеги могли бы также, если бы вы реализовали это таким образом!)
Короткий ответ (слишком поздно)
Указание о том, что не следует наследовать от классов коллекций, не относится к C #, вы найдете его во многих языках программирования. Получается мудрость, а не закон. Одна из причин заключается в том, что на практике считается, что композиция часто побеждает наследование с точки зрения понятности, реализуемости и ремонтопригодности. С объектами реального мира / домена чаще встречаются полезные и непротиворечивые отношения «hasa», чем полезные и непротиворечивые отношения «isa», если вы не углубляетесь в абстракцию, особенно когда проходит время и точные данные и поведение объектов в коде меняется. Это не должно заставлять вас всегда исключать наследование от классов коллекции; но это может быть наводящим на размышления.
источник
Прежде всего, это связано с удобством использования. Если вы используете наследование,
Team
класс будет демонстрировать поведение (методы), которые предназначены исключительно для манипулирования объектами. Например,AsReadOnly()
илиCopyTo(obj)
методы не имеют смысла для объекта команды. ВместоAddRange(items)
метода вы, вероятно, захотите более описательныйAddPlayers(players)
метод.Если вы хотите использовать LINQ, реализация общего интерфейса, такого как
ICollection<T>
или,IEnumerable<T>
будет иметь больше смысла.Как уже упоминалось, композиция - верный путь. Просто реализуйте список игроков как личную переменную.
источник
Футбольная команда - это не список футболистов. Футбольная команда состоит из списка футболистов!
Это логически неправильно:
и это правильно
источник
Это зависит от контекста
Когда вы рассматриваете свою команду как список игроков, вы проецируете «идею» команды по мячу для ног на один аспект: вы сводите «команду» к людям, которых вы видите на поле. Эта проекция верна только в определенном контексте. В другом контексте это может быть совершенно неправильно. Представьте, что вы хотите стать спонсором команды. Так что вы должны поговорить с менеджерами команды. В этом контексте команда проецируется на список ее менеджеров. И эти два списка обычно не сильно совпадают. Другие контексты - это текущие против бывших игроков и т. Д.
Непонятная семантика
Таким образом, проблема с рассмотрением команды как списка ее игроков заключается в том, что ее семантика зависит от контекста и что она не может быть расширена при изменении контекста. Кроме того, трудно выразить, какой контекст вы используете.
Классы расширяемые
Когда вы используете класс только с одним членом (например
IList activePlayers
), вы можете использовать имя члена (и дополнительно его комментарий), чтобы сделать контекст понятным. Когда есть дополнительные контексты, вы просто добавляете дополнительного участника.Занятия сложнее
В некоторых случаях создание дополнительного класса может оказаться излишним. Каждое определение класса должно быть загружено через загрузчик классов и будет кэшироваться виртуальной машиной. Это стоит вам времени выполнения и памяти. Если у вас очень специфический контекст, возможно, будет неплохо рассматривать футбольную команду в качестве списка игроков. Но в этом случае вы должны просто использовать
IList
класс, а не класс, производный от него.Заключение / Соображения
Когда у вас есть очень специфический контекст, можно рассматривать команду как список игроков. Например, внутри метода вполне нормально написать:
При использовании F # даже можно создать аббревиатуру типа:
Но когда контекст шире или даже неясен, вы не должны этого делать. Это особенно актуально, когда вы создаете новый класс, контекст которого в будущем может быть использован, неясен. Предупреждающий знак - это когда вы начинаете добавлять дополнительные атрибуты в ваш класс (название команды, тренер и т. Д.). Это явный признак того, что контекст, в котором будет использоваться класс, не является фиксированным и изменится в будущем. В этом случае вы не можете рассматривать команду как список игроков, но вы должны смоделировать список игроков (активных, не травмированных и т. Д.) Как атрибут команды.
источник
Позвольте мне переписать ваш вопрос. так что вы можете увидеть предмет с другой точки зрения.
Когда мне нужно представлять футбольную команду, я понимаю, что это в основном имя. Нравится: "Орлы"
Потом я понял, что в командах тоже есть игроки.
Почему я не могу просто расширить строковый тип, чтобы он также содержал список игроков?
Ваша точка входа в проблему является произвольной. Попытайтесь думать , что это команда имеет (свойства), а не то , что она есть .
После того, как вы это сделаете, вы сможете увидеть, разделяет ли он свойства с другими классами. И подумай о наследовании.
источник
Просто потому, что я думаю, что другие ответы в значительной степени отклоняются от того, является ли футбольная команда «есть»
List<FootballPlayer>
или «имеет»List<FootballPlayer>
, которая действительно не отвечает на этот вопрос в письменном виде.ФП, главным образом, просит уточнить руководящие принципы для наследования от
List<T>
:Потому
List<T>
что не имеет виртуальных методов. Это меньше проблем в вашем собственном коде, так как вы обычно можете переключить реализацию с относительно небольшой болью - но это может быть гораздо более серьезной сделкой в публичном API.Публичный API - это интерфейс, который вы предоставляете сторонним программистам. Подумайте рамки кода. И помните, что упомянутыми руководящими принципами являются « Руководящие указания по разработке .NET Framework », а не « Руководящие указания по разработке приложений .NET ». Есть разница, и, вообще говоря, дизайн публичного API намного более строг.
Да, довольно. Возможно, вы захотите рассмотреть обоснование, стоящее за этим, чтобы посмотреть, применимо ли это к вашей ситуации, но если вы не создаете общедоступный API, вам не нужно особенно беспокоиться о проблемах API, таких как управление версиями (из которых это подмножество).
Если вы добавите общедоступный API в будущем, вам нужно будет либо абстрагировать свой API от своей реализации (не подвергая его
List<T>
непосредственному воздействию ), либо нарушить рекомендации с возможной будущей болью, которая влечет за собой.Зависит от контекста, но поскольку мы используем
FootballTeam
в качестве примера - представьте, что вы не можете добавить a,FootballPlayer
если это приведет к тому, что команда пересечет верхний предел зарплаты. Возможный способ добавить что-то вроде:Ах ... но вы не можете,
override Add
потому что это не такvirtual
(по причинам производительности).Если вы находитесь в приложении (что, по сути, означает, что вы и все ваши вызывающие программы компилируются вместе), то теперь вы можете перейти к использованию
IList<T>
и исправить любые ошибки компиляции:но если вы публично предоставили информацию третьим лицам, вы просто внесли критическое изменение, которое приведет к ошибкам компиляции и / или выполнения.
TL; DR - рекомендации для публичных API. Для частных API делай что хочешь.
источник
Позволяет ли людям говорить
вообще имеет смысл? Если нет, то это не должно быть списком.
источник
myTeam.subTeam(3, 5);
subList
даже не вернетсяTeam
.Это зависит от поведения вашего «командного» объекта. Если он ведет себя так же, как коллекция, возможно, было бы неплохо сначала представить его простым списком. Затем вы можете начать замечать, что продолжаете дублировать код, который повторяется в списке; на данный момент у вас есть возможность создать объект FootballTeam, который оборачивает список игроков. Класс FootballTeam становится домом для всего кода, который повторяется в списке игроков.
Инкапсуляция. Вашим клиентам не нужно знать, что происходит внутри FootballTeam. Для всех ваших клиентов это может быть реализовано путем просмотра списка игроков в базе данных. Им не нужно знать, и это улучшает ваш дизайн.
Точно :) Вы скажете footballTeam.Add (john), а не footballTeam.List.Add (john). Внутренний список не будет виден.
источник
Здесь есть много отличных ответов, но я хочу коснуться кое-чего, о чем я не упоминал: объектно-ориентированный дизайн - это расширение возможностей объектов .
Вы хотите инкапсулировать все свои правила, дополнительную работу и внутренние детали внутри соответствующего объекта. Таким образом, другие объекты, взаимодействующие с этим, не должны беспокоиться обо всем этом. Фактически, вы хотите пойти дальше и активно препятствовать тому, чтобы другие объекты обходили эти внутренние механизмы .
Когда вы наследуете
List
, все другие объекты могут видеть вас как список. Они имеют прямой доступ к методам добавления и удаления игроков. И вы потеряете свой контроль; например:Предположим, вы хотите различить, когда игрок уходит, зная, вышел ли он в отставку, ушел в отставку или был уволен. Вы могли бы реализовать
RemovePlayer
метод, который принимает соответствующее входное перечисление. Однако, унаследовав отList
, вы не смогли бы предотвратить прямой доступ кRemove
,RemoveAll
и дажеClear
. В результате вы фактически лишили своегоFootballTeam
класса силы .Дополнительные мысли по поводу инкапсуляции ... Вы подняли следующую проблему:
Вы правы, это было бы излишне многословным для всех клиентов, чтобы использовать вашу команду. Тем не менее, эта проблема очень мала по сравнению с тем фактом, что вы сталкивались
List Players
со всеми и каждому, поэтому они могут возиться с вашей командой без вашего согласия.Вы продолжаете говорить:
Вы ошибаетесь по поводу первого бита: отбросьте слово «список», и на самом деле очевидно, что в команде есть игроки.
Однако вы ударяете ноготь по голове вторым. Вы не хотите, чтобы клиенты звонили
ateam.Players.Add(...)
. Вы хотите, чтобы они звонилиateam.AddPlayer(...)
. И ваша реализация будет (возможно, помимо прочего) вызыватьсяPlayers.Add(...)
внутри.Надеюсь, вы сможете увидеть, насколько важна инкапсуляция для расширения возможностей ваших объектов. Вы хотите, чтобы каждый класс хорошо выполнял свою работу, не боясь вмешательства других объектов.
источник
Помните: «Все модели ошибочны, но некоторые полезны». - Джордж EP Box
Не существует «правильного пути», только полезного.
Выберите тот, который полезен для вас и / ваших пользователей. Вот и все.
Развивайся экономно, не переусердствуй. Чем меньше кода вы пишете, тем меньше кода вам нужно будет отлаживать.(читайте следующие издания).- Отредактировано
Мой лучший ответ будет ... это зависит. Наследование от List подвергло бы клиентов этого класса методам, которые могут быть недоступны, прежде всего потому, что FootballTeam выглядит как бизнес-объект.
- Издание 2
Я искренне не помню, на что я ссылался в комментарии «не переусердствуйте». Хотя я считаю, что мышление KISS является хорошим руководством, я хочу подчеркнуть, что наследование бизнес-класса от List создаст больше проблем, чем решит, из-за утечки абстракции .
С другой стороны, я считаю, что существует ограниченное количество случаев, когда полезно просто наследовать от List. Как я уже писал в предыдущем издании, это зависит. Ответ на каждый случай в значительной степени зависит от знаний, опыта и личных предпочтений.
Спасибо @kai за то, что помогли мне более точно обдумать ответ.
источник
List
будет подвергать клиентов этого класса методам, которые, возможно, не должны подвергаться », - именно то, что делает наследование отList
абсолютного нет-нет. После того, как они подвергаются воздействию, они постепенно подвергаются насилию и неправильному использованию с течением времени. Экономия времени благодаря «экономическому развитию» может легко привести к десятикратной экономии в будущем: отладке злоупотреблений и в конечном итоге рефакторингу для исправления наследства. Принцип YAGNI также может рассматриваться как смысл: вам не нужны все эти методыList
, поэтому не подвергайте их воздействию.List
что это быстро и легко и, следовательно, приведет к уменьшению количества ошибок, является просто ошибкой, просто плохой дизайн и плохой дизайн приводят к гораздо большему количеству ошибок, чем «из-за инженерной мысли».Это напоминает мне о том, "Есть" против "имеет" компромисс. Иногда проще и имеет смысл наследовать напрямую от суперкласса. В других случаях имеет смысл создать автономный класс и включить класс, от которого вы бы унаследовали, в качестве переменной-члена. Вы все еще можете получить доступ к функциональности класса, но не привязаны к интерфейсу или другим ограничениям, которые могут возникнуть в результате наследования от класса.
Что ты делаешь? Как и во многих вещах ... это зависит от контекста. Руководство, которое я бы использовал, заключается в том, что для наследования от другого класса действительно должны быть отношения «есть». Так что, если вы пишете класс под названием BMW, он может унаследовать от Car, потому что BMW действительно является автомобилем. Класс Horse может наследоваться от класса Mammal, потому что лошадь в действительности является млекопитающим в реальной жизни, и любые функции Mammal должны иметь отношение к Horse. Но можете ли вы сказать, что команда - это список? Из того, что я могу сказать, не похоже, что команда действительно "является" списком. Так что в этом случае, я бы имел List в качестве переменной-члена.
источник
Мой грязный секрет: мне все равно, что говорят люди, и я делаю это. .NET Framework распространяется с помощью «XxxxCollection» (пример UIElementCollection в верхней части моей головы).
Так что мне мешает сказать:
Когда я нахожу это лучше, чем
Более того, мой PlayerCollection может использоваться другим классом, например, «Club» без какого-либо дублирования кода.
Лучшие практики вчерашнего дня, возможно, не завтрашние. За большинством лучших практик нет никаких оснований, большинство из них - это лишь широкое согласие сообщества. Вместо того, чтобы спрашивать сообщество, будет ли оно винить вас, когда вы это делаете, спросите себя, что является более читабельным и понятным?
или
В самом деле. У тебя есть сомнения? Теперь, возможно, вам нужно поиграть с другими техническими ограничениями, которые мешают вам использовать List <T> в вашем реальном случае использования. Но не добавляйте ограничение, которое не должно существовать. Если Microsoft не задокументировала причину, то это, безусловно, «лучшая практика», пришедшая из ниоткуда.
источник
Collection<T>
это не то же самое,List<T>
что может потребоваться немало усилий для проверки в крупномасштабном проекте.team.ByName("Nicolas")
означает"Nicolas"
имя команды.В руководящих принципах говорится, что общедоступный API-интерфейс не должен раскрывать внутреннее дизайнерское решение о том, используете ли вы список, набор, словарь, дерево или что-то еще. «Команда» - это не обязательно список. Вы можете реализовать его в виде списка, но пользователи вашего публичного API должны использовать ваш класс по мере необходимости. Это позволяет вам изменить свое решение и использовать другую структуру данных, не затрагивая общедоступный интерфейс.
источник
class FootballTeam : List<FootballPlayers>
, пользователи моего класса смогут сказать мне: Мы унаследовали отList<T>
использования рефлексии, увиделиList<T>
методы, которые не имеют смысла для футбольной команды, и смогли использоватьFootballTeam
ихList<T>
, поэтому я бы раскрыл детали реализации клиенту (без необходимости).Когда они говорят, что они
List<T>
«оптимизированы», я думаю, они хотят иметь в виду, что у них нет таких функций, как виртуальные методы, которые немного дороже. Таким образом, проблема заключается в том, что, как только вы открываете доступList<T>
к общедоступному API , вы теряете возможность применять бизнес-правила или настраивать его функциональность позже. Но если вы используете этот унаследованный класс как внутренний в своем проекте (в отличие от потенциально уязвимого для тысяч ваших клиентов / партнеров / других команд в качестве API), то, возможно, все будет в порядке, если он экономит ваше время и вам нужна эта функциональность. дублировать. Преимущество наследования от этогоList<T>
состоит в том, что вы устраняете много глупого кода-обертки, который никогда не будет настроен в обозримом будущем. Также, если вы хотите, чтобы ваш класс явно имел точно такую же семантику, что иList<T>
для жизни ваших API, то это также может быть в порядке.Я часто вижу множество людей, выполняющих тонны дополнительной работы только потому, что так говорит правило FxCop, или чей-то блог говорит, что это «плохая» практика. Много раз, это превращает код в дизайн паттерна странности. Как и в случае с большинством рекомендаций, относитесь к ним как к руководству, которое может иметь исключения.
источник
Хотя у меня нет сложного сравнения, как большинство из этих ответов, я хотел бы поделиться своим методом для решения этой ситуации. Расширяя
IEnumerable<T>
, вы можете позволить вашемуTeam
классу поддерживать расширения запросов Linq, не раскрывая публично все методы и свойстваList<T>
.источник
Если пользователям вашего класса нужны все методы и свойства **, которые есть в List, вы должны извлечь из него свой класс. Если они им не нужны, приложите List и создайте оболочки для методов, которые действительно нужны вашим классным пользователям.
Это строгое правило, если вы пишете публичный API или любой другой код, который будет использоваться многими людьми. Вы можете игнорировать это правило, если у вас крошечное приложение и не более 2 разработчиков. Это сэкономит вам время.
Для крошечных приложений вы также можете выбрать другой, менее строгий язык. Ruby, JavaScript - все, что позволяет писать меньше кода.
источник
Я просто хотел добавить, что Бертран Мейер, изобретатель Eiffel и дизайн по контракту,
Team
унаследовал бы отList<Player>
того, что стукнуло веко.В своей книге « Построение объектно-ориентированного программного обеспечения» он обсуждает реализацию системы с графическим интерфейсом, в которой у прямоугольных окон могут быть дочерние окна. Он просто должен
Window
наследовать от обоихRectangle
иTree<Window>
повторно использовать реализацию.Тем не менее, C # не является Eiffel. Последний поддерживает множественное наследование и переименование функций . В C # при создании подкласса вы наследуете как интерфейс, так и реализацию. Вы можете переопределить реализацию, но соглашения о вызовах копируются непосредственно из суперкласса. В Eiffel, однако, вы можете изменить имена общедоступных методов, так что вы можете переименовать
Add
иRemove
вHire
иFire
в вашемTeam
. Если экземплярTeam
передается обратно вList<Player>
, вызывающая сторона будет использоватьAdd
иRemove
изменять его, но ваши виртуальные методыHire
иFire
будут вызваны.источник
Я думаю, что я не согласен с вашим обобщением. Команда - это не просто набор игроков. У команды есть намного больше информации об этом - имя, эмблема, коллекция управленческого / административного персонала, коллекция тренерской команды, затем коллекция игроков. Таким образом, ваш класс FootballTeam должен иметь 3 коллекции, а не сам набор; если это правильно моделировать реальный мир.
Вы могли бы рассмотреть класс PlayerCollection, который, как и Specialized StringCollection, предлагает некоторые другие средства, такие как проверка и проверка перед добавлением или удалением объектов из внутреннего хранилища.
Возможно, понятие Better PlayerCollection подходит вашему предпочтительному подходу?
И тогда FootballTeam может выглядеть так:
источник
Предпочитаю интерфейсы классам
Классы должны избегать наследования от классов и реализовывать минимально необходимые интерфейсы.
Наследование нарушает инкапсуляцию
Вывод из классов нарушает инкапсуляцию :
Среди прочего это усложняет рефакторинг вашего кода.
Классы - это деталь реализации
Классы - это детали реализации, которые должны быть скрыты от других частей вашего кода. Короче говоря, это
System.List
конкретная реализация абстрактного типа данных, которая может быть или не быть подходящей сейчас и в будущем.Концептуально тот факт, что
System.List
тип данных называется списком, является чем-то вроде красной селедки. ASystem.List<T>
- это изменяемая упорядоченная коллекция, которая поддерживает амортизированные операции O (1) для добавления, вставки и удаления элементов, а также операции O (1) для получения количества элементов или получения и установки элемента по индексу.Чем меньше интерфейс, тем более гибкий код
При проектировании структуры данных, чем проще интерфейс, тем более гибок код. Просто посмотрите, насколько мощный LINQ для демонстрации этого.
Как выбрать интерфейсы
Когда вы думаете «список», вы должны начать говорить себе: «Мне нужно представлять коллекцию бейсболистов». Допустим, вы решили смоделировать это с помощью класса. Сначала вы должны решить, какое минимальное количество интерфейсов необходимо предоставить этому классу.
Некоторые вопросы, которые могут помочь в этом процессе:
IEnumerable<T>
IReadonlyList<T>
.ICollection<T>
ISet<T>
?IList<T>
.Таким образом, вы не будете связывать другие части кода с деталями реализации вашей коллекции игроков в бейсбол и будете свободны изменять ее реализацию, если вы уважаете интерфейс.
Используя этот подход, вы обнаружите, что код становится проще для чтения, рефакторинга и повторного использования.
Заметки о том, как избежать Boilerplate
Реализация интерфейсов в современной IDE должна быть простой. Щелкните правой кнопкой мыши и выберите «Интерфейс реализации». Затем перенаправьте все реализации в класс-член, если вам нужно.
Тем не менее, если вы обнаружите, что пишете много шаблонов, это возможно потому, что вы раскрываете больше функций, чем должны быть. Это та же самая причина, по которой вы не должны наследовать от класса.
Вы также можете разработать меньшие интерфейсы, которые имеют смысл для вашего приложения, и, возможно, просто пару вспомогательных функций расширения, чтобы сопоставить эти интерфейсы с любыми другими, которые вам нужны. Это подход, который я использовал в своем собственном
IArray
интерфейсе для библиотеки LinqArray .источник
Проблемы с сериализацией
Один аспект отсутствует. Классы, которые наследуются от List <>, не могут быть правильно сериализованы с помощью XmlSerializer . В этом случае вместо этого должен использоваться DataContractSerializer или необходима собственная реализация сериализации.
Дополнительная информация: Когда класс наследуется от List <>, XmlSerializer не сериализует другие атрибуты.
источник
Цитирую Эрика Липперта:
Например, вы устали от отсутствия
AddRange
метода вIList<T>
:источник