Преимущество упаковки Список & lt; T & gt; в список классов [дубликат]

В java все ссылается, поэтому, когда у вас есть что-то вроде: Point pnt1 = new Point(0,0); Java делает следующее:

  1. Создает новый объект Point
  2. Создает новую ссылку на точку и инициализирует эта ссылка на точку (см.) на ранее созданном объекте Point.
  3. Отсюда, через жизнь объекта Point, вы получите доступ к этому объекту через ссылку pnt1. Поэтому мы можем сказать, что в Java вы манипулируете объектом по его ссылке.

enter image description here [/g7]

Java не передает аргументы метода по ссылке; он передает их по значению. Я буду использовать пример из этого сайта :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

Поток программы:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Создание двух разных объектов Point с двумя различными ссылками , enter image description here [/g8]

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

Как и ожидалось, выход будет:

X1: 0     Y1: 0
X2: 0     Y2: 0

В этой строке «pass-by-value» переходит в игру ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

Ссылки pnt1 и pnt2 передаются по значению сложному методу, что означает, что теперь ваши ссылки pnt1 и pnt2 имеют свои copies с именем arg1 и arg2. Так же pnt1 и arg1 указывают на тот же объект. (То же самое для pnt2 и arg2) enter image description here [/g9]

В методе tricky:

 arg1.x = 100;
 arg1.y = 100;

enter image description here [/g10]

Далее в методе tricky

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Здесь вы сначала создаете новую ссылку temp Point, которая будет point в том же месте, что и arg1 справка. Затем вы перемещаете ссылку arg1 в точку в то же место, что и arg2. Наконец, arg2 будет point в том же месте, что и temp.

enter image description here [/g11]

Отсюда область действия tricky метод ушел, и вы больше не имеете доступа к ссылкам: arg1, arg2, temp. Но важно отметить, что все, что вы делаете с этими ссылками, когда они «в жизни», будет постоянно влиять на объект, на который они указывают .

Итак, после выполнения метода tricky, когда вы вернетесь к main, у вас есть такая ситуация: enter image description here [/g12]

Итак, теперь полное выполнение программы будет be:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0

1041
задан Community 23 May 2017 в 11:54
поделиться

26 ответов

Здесь есть хорошие ответы. Я бы добавил к ним следующие моменты.

Каков правильный способ C # для представления структуры данных, которая «логически» (то есть «для человеческого разума») просто список вещей с несколькими колокольчиками?

Спросите любого десяти людей, не являющихся компьютерными программистами, которые знакомы с наличием футбола, чтобы заполнить пробел:

A football team is a particular kind of _____

Кто-нибудь сказал «список футболистов с несколькими колокольчиками», или все они говорили «спортивная команда» или «клуб» или «организация»? Ваше мнение о том, что футбольная команда - это особый список игроков , - это ваш ум и ваш человеческий разум.

List<T> является механизмом . Футбольная команда является бизнес-объектом , то есть объектом, который представляет собой концепцию, которая находится в бизнес-домене программы. Не смешивайте их! Футбольная команда - это своего рода команда ; у него есть список , список - список игроков . Реестр не является конкретным списком игроков . Список - список игроков. Поэтому создайте свойство, называемое Roster, которое является List<Player>. И сделайте это ReadOnlyList<Player>, пока вы на нем, если не верите, что все, кто знает о футбольной команде, удаляют игроков из списка.

Наследуется от List<T> всегда неприемлемо ?

Неприемлемо для кого? Меня? Нет.

Когда это приемлемо?

Когда вы создаете механизм, который расширяет механизм List<T> .

Что должен знать программист, когда решая, наследовать ли от List<T> или нет?

Я строю механизм или бизнес-объект ?

Но это много кода! Что я получаю за все, что работает?

Вы потратили больше времени, набрав свой вопрос о том, что потребовалось бы пятьдесят раз написать методы пересылки для соответствующих членов List<T>. Вы явно не боитесь многословия, и здесь мы говорим о очень небольшом количестве кода; это несколько минут работы.

UPDATE

Я немного подумал, и есть еще одна причина не моделировать футбольную команду как список игроков. На самом деле может быть плохой идеей моделировать футбольную команду, поскольку имеет список игроков. Проблема с командой, имеющей список игроков, заключается в том, что у вас есть моментальный снимок команды в момент времени . Я не знаю, каково ваше деловое дело для этого класса, но если бы у меня был класс, представлявший футбольную команду, я бы хотел задать ему вопросы: «Сколько игроков Seahawks пропустили игры из-за травмы в период с 2003 по 2013 год?» или «Какой игрок в Денвере, который ранее играл за другую команду, имел наибольшее увеличение по годам в ярдах?» или « В этом году все пигеры прошли весь путь? »

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

1143
ответ дан Eric Lippert 15 August 2018 в 18:36
поделиться
  • 1
    Хотя другие ответы были очень полезными, я думаю, что это касается моих проблем наиболее непосредственно. Что касается преувеличения объема кода, вы правы в том, что в конечном итоге это не так много, но я очень запутался, когда включил в программу какой-то код, не понимая, почему я его включаю. – Superbest 11 February 2014 в 08:19
  • 2
    @Superbest: Рад, что я мог бы помочь! Вы правы, чтобы выслушать эти сомнения; если вы не понимаете, почему вы пишете какой-либо код, либо выясните его, либо напишите другой код, который вы понимаете. – Eric Lippert 11 February 2014 в 08:25
  • 3
    @Mehrdad Но вы, похоже, забываете о золотых правилах оптимизации: 1) не делайте этого, 2) еще не делайте этого, и 3) не делайте этого, не делая сначала профилей производительности, чтобы показать, что нужно оптимизировать. – AJMansfield 11 February 2014 в 14:58
  • 4
    @Mehrdad: Честно говоря, если ваше приложение требует, чтобы вы заботились о бремени производительности, скажем, виртуальных методов, то любой современный язык (C #, Java и т. Д.) Не является для вас языком. У меня есть ноутбук прямо здесь, который может запускать симуляцию каждой аркадной игры, которую я играл как дочерний одновременно . Это позволяет мне не беспокоиться об уровне косвенности здесь и там. – Eric Lippert 11 February 2014 в 16:37
  • 5
    @Superbest: теперь мы определенно в "композиции не наследования" конец спектра. Ракета состоит из частей ракеты . Ракета не является «особым видом запасных частей». Я предлагаю вам, чтобы вы поправили его дальше; почему список, в отличие от последовательности IEnumerable<T> - a ? – Eric Lippert 11 February 2014 в 20:15

Я просто хотел добавить, что Бертран Мейер, изобретатель Эйфеля и дизайн по контракту, наследовал бы Team от List<Player>, не так как моргнув веком.

В своей книге, Объектно-ориентированное программное обеспечение , он обсуждает реализацию GUI-системы, в которой прямоугольные окна могут иметь дочерние окна. Он просто имеет Window наследовать от Rectangle и Tree<Window> для повторного использования реализации.

Однако C # не является Eiffel. Последний поддерживает множественное наследование и переименование функций . В C #, когда вы выполняете подкласс, вы наследуете интерфейс и реализацию. Вы можете переопределить реализацию, но соглашения о вызовах копируются непосредственно из суперкласса. Однако в Eiffel вы можете изменить имена общедоступных методов, поэтому вы можете переименовать Add и Remove в Hire и Fire в свой Team. Если экземпляр Team снова возвращается к List<Player>, вызывающий будет использовать Add и Remove для его изменения, но ваши виртуальные методы Hire и Fire будут вызваны.

3
ответ дан Alexey 15 August 2018 в 18:36
поделиться
  • 1
    Здесь проблема заключается не в множественном наследовании, mixins (и т. Д.), Либо в том, как справляться с их отсутствием. На любом языке, даже на человеческом языке, команда не является списком людей, но состоит из списка людей. Это абстрактное понятие. Если Бертран Мейер или кто-либо управляет командой путем подклассификации List, это делает неправильно. Вы должны подклассифицировать Список, если хотите более конкретный вид списка. Надеюсь, вы согласитесь. – sam 7 July 2015 в 13:57
  • 2
    Это зависит от того, какое наследство вам принадлежит. Сама по себе это абстрактная операция, это не означает, что два класса находятся в «особом типе». отношения, хотя это интерпретация в основном тексте. Однако наследование может рассматриваться как механизм повторного использования реализации, если его поддерживает язык, хотя композиция является пересмотренной альтернативой в настоящее время. – Alexey 7 July 2015 в 19:12

Каков правильный способ C # для представления структуры данных ...

Remeber: «Все модели ошибочны, но некоторые из них полезны». - George EP Box

Нет «правильного пути», только полезный.

Выберите тот, который вам полезен и / ваши пользователи , Вот и все. Развивайте экономически, не переучивайте. Чем меньше кода вы пишете, тем меньше кода вам нужно будет отлаживать. (читайте следующие выпуски).

- Отредактировано

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

- Издание 2

Я искренне не помню, что я имел в виду в комментарии «не переучивайтесь». Хотя я считаю, что мышление KISS является хорошим ориентиром, я хочу подчеркнуть, что наследование бизнес-класса из списка создаст больше проблем, чем устраняет, из-за утечки абстракции .

С другой стороны, я считаю, что существует ограниченное число случаев, когда просто наследовать из Списка полезно. Как я писал в предыдущем издании, все зависит. Ответ на каждый случай сильно зависит от знания, опыта и личных предпочтений.

Спасибо @kai за то, что он помог мне более точно рассказать о ответе.

11
ответ дан Arturo Tena 15 August 2018 в 18:36
поделиться
  • 1
    Мой лучший ответ был бы ... это зависит. Наследование из списка приведет к тому, что клиенты этого класса будут подвергнуты методам, которые могут не отображаться, прежде всего потому, что FootballTeam выглядит как бизнес-объект. Я не знаю, должен ли я отредактировать этот ответ или опубликовать другое, любую идею? – Arturo Tena 11 February 2014 в 20:23
  • 2
    Бит о «, наследующий от List, будет подвергать клиентов этого класса методам, которые могут не подвергаться экспонату & quot; именно то, что делает наследование от List абсолютным no-no. После разоблачения они постепенно становятся злоупотребляемыми и неправильно используемыми с течением времени. Экономия времени в настоящее время путем «экономичного развития» может легко привести к десятикратному сокращению сбережений в будущем: отладка злоупотреблений и, в конечном счете, рефакторинг для исправления наследования. Принцип YAGNI также можно представить как значение: вам не нужны все эти методы из List, поэтому не подвергайте их воздействию. – Craig Young 15 February 2014 в 20:59
  • 3
    «не переусердствовать». Чем меньше кода вы пишете, тем меньше кода вам нужно будет отлаживать. & Quot; Я считаю, что это вводит в заблуждение. Инкапсуляция и состав над наследованием НЕ являются чрезмерной инженерией, и это не вызывает больше необходимости в отладке. Инкапсулируя, вы ОГРАНИЧИВАЕТ количество способов, которыми клиенты могут использовать (и злоупотреблять) ваш класс и, следовательно, у вас меньше точек входа, требующих тестирования и проверки ввода. Унаследовать от List, потому что это быстро и просто и, следовательно, приведет к уменьшению количества ошибок, это просто неправильно, это просто плохой дизайн и плохой дизайн приводит к гораздо большему количеству ошибок, чем «над разработкой». – kai 15 September 2015 в 15:40
  • 4
    @kai Я согласен с вами в каждой точке. Я искренне не помню, что я имел в виду в комментарии «Не переучивайте». OTOH, я считаю, что существует ограниченное число случаев, когда просто наследовать из Списка полезно. Как я писал в более позднем издании, это зависит. Ответ на каждый случай сильно зависит от знания, опыта и личных предпочтений. Как все в жизни. ;-) – Arturo Tena 15 September 2015 в 22:20

Ничего себе, ваш пост имеет целую массу вопросов и вопросов. Большинство аргументов, которые вы получаете от Microsoft, точно на месте. Начнем со всего, что касается List<T>

  • List<T> сильно оптимизировано. Это основное использование должно использоваться как частный член объекта.
  • Microsoft не запечатывала его, потому что иногда вам может понадобиться создать класс с более дружелюбным именем: class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }. Теперь это так же просто, как делать var list = new MyList<int, string>();.
  • CA1002: не выставлять общие списки : В принципе, даже если вы планируете использовать это приложение в качестве единственного разработчика, стоит развиваются с хорошей практикой кодирования, поэтому они становятся привитыми вам и второй природе. Вам по-прежнему разрешено раскрывать список как IList<T>, если вам нужен какой-либо потребитель для индексации списка. Это позволит вам изменить реализацию в классе позже.
  • Microsoft сделала Collection<T> очень общей, потому что это общая концепция ... имя говорит все; это просто коллекция. Существуют более точные версии, такие как SortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T> и т. Д., Каждый из которых реализует IList<T>, но не List<T>.
  • Collection<T> позволяет использовать элементы (например, Add , Удалить и т. Д.) Для переопределения, поскольку они являются виртуальными. List<T> нет.
  • Последняя часть вашего вопроса находится на месте. Футбольная команда - это не просто список игроков, поэтому он должен быть классом, который содержит этот список игроков. Подумайте Композиция против наследования . Футбольная команда имеет список игроков (список), это не список игроков.

Если бы я писал этот код, класс, вероятно, будет выглядеть примерно так:

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}
110
ответ дан Community 15 August 2018 в 18:36
поделиться
  • 1
    « ваше сообщение имеет целую тему вопросов & quot; - Ну, я сказал, что я был очень смущен. Спасибо за ссылку на вопрос @ Readonly, теперь я вижу, что большая часть моего вопроса - это особый случай более общей проблемы с композицией и наследованием. – Superbest 11 February 2014 в 07:11
  • 2
    @Brian: Если вы не можете создать общий псевдоним, все типы должны быть конкретными. В моем примере List<T> расширен как частный внутренний класс, который использует обобщения для упрощения использования и объявления List<T>. Если расширение было всего лишь class FootballRoster : List<FootballPlayer> { }, то да, я мог бы использовать псевдоним вместо этого. Я предполагаю, что стоит отметить, что вы можете добавить дополнительных членов / функциональность, но она ограничена, так как вы не можете переопределить важных членов List<T>. – m-y 11 February 2014 в 18:33
  • 3
    Если бы это были Programmers.SE, я бы согласился с текущим диапазоном ответов, но, поскольку это сообщение SO, этот ответ хотя бы пытается ответить на конкретные проблемы C # (.NET), хотя есть определенные общие вопросы проектирования. – Mark Hurd 12 February 2014 в 04:10
  • 4
    Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden Теперь , что является хорошей причиной не наследовать от List. Остальные ответы имеют слишком много философии. – Navin 12 February 2014 в 05:54
  • 5
    @ m-y: Я подозреваю, что вы имеете в виду & quot; slew & quot; вопросов, поскольку сыщик - детектив. – batty 16 February 2014 в 18:28

Здесь есть много отличных ответов, но я хочу затронуть то, о чем я не упоминал: Объектно-ориентированный дизайн посвящен полномочным объектам .

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

Когда вы наследуете List, все остальные объекты могут видеть вас как Список. Они имеют прямой доступ к методам добавления и удаления игроков. И ты потеряешь контроль; например:

Предположим, вы хотите различать, когда игрок уходит, зная, удалены ли они, ушли или уволены. Вы можете реализовать метод RemovePlayer, который принимает соответствующее перечисление ввода. Однако, наследуя от List, вы не сможете предотвратить прямой доступ к Remove, RemoveAll и даже Clear. В результате вы фактически лишили вашего класса FootballTeam.


Дополнительные мысли об инкапсуляции ... Вы подняли следующее беспокойство:

< blockquote>

Это делает мой код ненужным подробным. Теперь я должен вызывать my_team.Players.Count, а не только my_team.Count.

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

Вы продолжаете говорить:

Это просто не имеет никакого смысла. Футбольная команда не имеет «списка» игроков. Это список игроков. Вы не говорите: «Джон Макфуталлер присоединился к игрокам SomeTeam». Вы говорите: «Джон присоединился к SomeTeam».

Вы ошибаетесь в первом бит: оставьте слово «список», и на самом деле очевидно, что в команде есть игроки. Тем не менее, вы ударили ноготь по голове вторым. Вы не хотите, чтобы клиенты вызывали ateam.Players.Add(...). Вы действительно хотите, чтобы они набрали ateam.AddPlayer(...). И ваша реализация (возможно, между прочим) вызовет Players.Add(...) внутренне.


Надеюсь, вы увидите, насколько важна инкапсуляция для цели расширения ваших объектов. Вы хотите, чтобы каждый класс хорошо выполнял свою работу, не опасаясь вмешательства с других объектов.

10
ответ дан Craig Young 15 August 2018 в 18:36
поделиться

Позволяет ли людям говорить

myTeam.subList(3, 5);

вообще иметь смысл? Если нет, то это не должно быть списком.

14
ответ дан Cruncher 15 August 2018 в 18:36
поделиться
  • 1
    Возможно, если вы его назвали myTeam.subTeam(3, 5); – Sam Leach 14 February 2014 в 23:01
  • 2
    @SamLeach Предполагая, что это правда, вам все равно потребуется композиция, а не наследование. Поскольку subList больше не вернет Team. – Cruncher 18 February 2014 в 17:52

Прежде всего, это связано с удобством использования. Если вы используете наследование, класс Team будет выставлять поведение (методы), которые предназначены исключительно для манипулирования объектами. Например, методы AsReadOnly() или CopyTo(obj) не имеют смысла для объекта команды. Вместо метода AddRange(items) вы, вероятно, захотите получить более описательный метод AddPlayers(players).

Если вы хотите использовать LINQ, реализация универсального интерфейса, такого как ICollection<T> или IEnumerable<T>, имеет смысл .

Как уже упоминалось, композиция - это правильный путь. Просто реализуйте список игроков как приватную переменную.

27
ответ дан Dmitry S. 15 August 2018 в 18:36
поделиться

Дизайн> Реализация

Какие методы и свойства вы раскрываете - это дизайнерское решение. Какой базовый класс вы наследуете, это деталь реализации. Я чувствую, что стоит сделать шаг назад к первому.

Объект - это совокупность данных и поведения.

Итак, ваши первые вопросы должны быть:

  • Какие данные этот объект содержится в модели, которую я создаю?
  • Какое поведение этот объект проявляет в этой модели? [ 1111]
  • Как это может измениться в будущем?

Имейте в виду, что наследование подразумевает отношение «isa» (a), тогда как композиция подразумевает «имеет» (hasa) связь. Выберите правильный вариант для вашей ситуации в своем представлении, учитывая, что ситуация может измениться по мере развития вашего приложения.

Подумайте о мышлении в интерфейсах, прежде чем думать о конкретных типах, поскольку некоторым людям легче поставить свой мозг в «режим дизайна» таким образом.

Это не то, что каждый делает сознательно на этом уровне в повседневной кодировке. Но если вы обдумываете такую ​​тему, вы стучите в проектные воды. Осознание этого может быть освобождением.

Рассмотрим конструкторские особенности

Взгляните на список & lt; T & gt; и IList & lt; T & gt; на MSDN или Visual Studio. Посмотрите, какие методы и свойства они выставляют. Считаете ли вы, что эти методы выглядят так, как будто кто-то хочет сделать с FootballTeam на ваш взгляд?

Does footballTeam.Reverse () имеет для вас смысл? Будет ли footballTeam.ConvertAll & lt; TOutput & gt; () выглядеть так, как вы хотите?

Это не спорный вопрос; ответ может быть действительно «да». Если вы реализуете / наследуете List & lt; Player & gt; или IList & lt; Player & gt ;, вы застряли с ними; если это идеально для вашей модели, сделайте это.

Если вы решите «да», это имеет смысл, и вы хотите, чтобы ваш объект обрабатывался как коллекция / список игроков (поведение), и поэтому вы хотите реализовать ICollection или IList, во что бы то ни стало. Понятно:

class FootballTeam : ... ICollection<Player>
{
    ...
}

Если вы хотите, чтобы ваш объект содержал коллекцию / список игроков (данных), и поэтому вы хотите, чтобы коллекция или список являлись свойством или членом, непременно сделайте это. Понятно:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

Возможно, вы захотите, чтобы люди могли только перечислять множество игроков, а не считать их, добавлять к ним или удалять их. IEnumerable & л; Игрок & GT; является вполне допустимым вариантом для рассмотрения.

Вы можете почувствовать, что ни один из этих интерфейсов не является полезным в вашей модели вообще. Это менее вероятно (IEnumerable & lt; T & gt; полезен во многих ситуациях), но это все еще возможно.

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

Перейдите к реализации

. Когда вы решите данные и поведение, вы можете принять решение о реализации. Это включает в себя конкретные классы, от которых вы зависите от наследования или композиции.

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

Эксперимент с мыслями

Искусственный пример: как отмечали другие, команда не всегда является «просто» коллекцией игроков. Поддерживаете ли вы коллекцию партитур для команды? Является ли команда взаимозаменяема с клубом, в вашей модели? Если это так, и если ваша команда является коллекцией игроков, возможно, она также является коллекцией персонала и / или коллекцией баллов. Тогда вы закончите:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

Несмотря на дизайн, на данном этапе C # вы не сможете реализовать все это путем наследования из List & lt; T & gt; так или иначе, поскольку C # "only" поддерживает одиночное наследование. (Если вы пробовали этот malarky в C ++, вы можете считать это хорошим делом). Реализация одной коллекции через наследование и одна с помощью композиции, вероятно, показала грязной. И такие свойства, как Count, становятся запутанными для пользователей, если вы явно не реализуете ILIst & lt; Player & gt; .Count и IList & lt; StaffMember & gt; .Count и т. Д., А затем они просто болезненны, а не запутывают. Вы можете видеть, где это происходит; (а также правильно или неправильно, ваши коллеги могут также, если бы вы реализовали его таким образом!)

Короткий ответ (слишком поздно)

. Руководство по не наследованию классов коллекций не является специфичным для C #, вы найдете его на многих языках программирования. Получается мудрость, а не закон. Одна из причин заключается в том, что на практике считается, что композиция часто выигрывает над наследованием с точки зрения понятности, реализуемости и ремонтопригодности. Чаще всего с объектами реального мира / домена можно найти полезные и последовательные отношения «hasa», чем полезные и последовательные отношения «isa», если вы не вникаете в абстрактные, особенно с течением времени и точные данные и поведение объектов в коде изменения. Это не должно заставлять вас всегда исключать наследование из классов коллекции; но это может быть наводящим на размышления.

25
ответ дан El Zorko 15 August 2018 в 18:36
поделиться

Думаю, я не согласен с вашим обобщением. Команда - это не просто коллекция игроков. У команды есть гораздо больше информации об этом - имя, эмблема, сборник управленческого / административного персонала, сбор коучинговой команды, а затем сбор игроков. Таким образом, ваш класс FootballTeam должен иметь 3 коллекции, а не собственную коллекцию; если он должен правильно моделировать реальный мир.

Вы можете рассмотреть класс PlayerCollection, который, как специализированный StringCollection, предлагает некоторые другие возможности - например, проверку и проверку перед добавлением или удалением объектов из внутреннего хранилища.

Возможно, понятие PlayerCollection лучше подходит вашему предпочтительному подходу?

public class PlayerCollection : Collection<Player> 
{ 
}

И тогда FootballTeam может выглядеть так:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}
0
ответ дан Eniola 15 August 2018 в 18:36
поделиться

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

Это строгое правило, если вы пишете публичный API или любой другой код, который будет использоваться многими людьми. Вы можете игнорировать это правило, если у вас есть крошечное приложение и не более 2 разработчиков. Это сэкономит вам время.

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

3
ответ дан Ivan Nikitin 15 August 2018 в 18:36
поделиться
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

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

People playing football [/g0]

В любом случае, этот код (из моего ответа)

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
    get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

означает: это футбольная команда, в которой есть руководство, игроки, администраторы и т. д. Что-то вроде:

Image showing members (and other attributes) of the Manchester United team [/g1]

Вот как ваша логика представлена ​​на фотографиях ...

104
ответ дан Justin Krejcha 15 August 2018 в 18:36
поделиться
  • 1
    Все, что нужно сейчас, это метод SackManager () ... – Phil Cooper 22 December 2014 в 11:09
  • 2
    Я бы ожидал, что ваш второй пример - футбольный клуб, а не футбольная команда. Футбольный клуб имеет менеджеров и администраторов и т. Д. Из этого источника : «Футбольная команда - это коллективное название группы игроков ... Такие команды могут быть выбраны играть в матче с противоположной командой, представлять футбольный клуб ... ». Поэтому я считаю, что команда - это список игроков (или, может быть, более точно коллекция игроков). – Ben 13 April 2015 в 17:13
  • 3
    Оптимизировано private readonly List<T> _roster = new List<T>(44); – SiD 20 July 2016 в 12:24
  • 4
    Необходимо импортировать пространство имен System.Linq. – Davide Cannizzo 12 December 2017 в 16:18

Просто потому, что я думаю, что другие ответы в значительной степени касаются касательной того, является ли футбольная команда «is-a» List<FootballPlayer> или «has-a» List<FootballPlayer>, которая на самом деле не отвечает на этот вопрос, как написано .

ОП в основном просит уточнить принципы наследования с List<T>:

. В руководстве говорится, что вы не должны наследовать List<T>. Почему нет?

Поскольку List<T> не имеет виртуальных методов. Это меньше проблем в вашем собственном коде, поскольку вы обычно можете отключить реализацию с относительно небольшой болью, но может быть гораздо более крупной сделкой в ​​публичном API.

Что такое публикация API и почему мне это нужно?

Открытый API - это интерфейс, который вы предоставляете сторонним программистам. Подумайте о каркасе. И помните, что ссылки, на которые ссылаются, - это «Рекомендации по дизайну .NET Framework », а не «Рекомендации по дизайну .NET ». Существует разница, и, вообще говоря, публичный дизайн API гораздо более строгий.

Если мой текущий проект не имеет и вряд ли когда-либо будет иметь этот открытый API, могу ли я безопасно игнорировать это руководство? Если я наследую List, и мне кажется, что мне нужен публичный API, какие трудности у меня есть?

В значительной степени, да. Возможно, вам стоит подумать об обоснованности этого вопроса, чтобы убедиться, что оно применимо к вашей ситуации в любом случае, но если вы не создаете публичный API, вам не нужно беспокоиться об особенностях API, таких как управление версиями (из которых это подмножество).

Если вы добавите публичный API в будущем, вам нужно будет либо абстрагировать свой API от вашей реализации (не подвергая непосредственно ваше List<T>), либо нарушать рекомендации с возможным будущим боль, которая влечет за собой.

Почему это даже имеет значение? Список - это список. Что может измениться? Что я могу изменить?

Зависит от контекста, но поскольку мы используем FootballTeam в качестве примера - представьте, что вы не можете добавить FootballPlayer, если он приведет к тому, что команда перейдет на зарплату. Возможный способ добавить это будет следующим:

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

А ... но вы не можете override Add, потому что это не virtual (по соображениям производительности).

Если вы находитесь в приложении (которое в основном означает, что вы и все ваши собеседники скомпилированы вместе), вы можете теперь изменить на IList<T> и исправить любые ошибки компиляции:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

, но если вы публично выступили с третьим лицом, вы только что внесли изменения, которые могут привести к ошибкам компиляции и / или времени выполнения.

TL; DR - руководящие принципы для общедоступных API. Для частных API выполните то, что вы хотите.

14
ответ дан Mark Brackett 15 August 2018 в 18:36
поделиться

Хотя у меня нет сложного сравнения, как большинство из этих ответов, я хотел бы поделиться своим методом для обработки этой ситуации. Расширяя IEnumerable<T>, вы можете позволить вашему классу Team поддерживать расширения запросов Linq, не публично раскрывая все методы и свойства List<T>.

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}
2
ответ дан Null511 15 August 2018 в 18:36
поделиться

Это напоминает мне о «есть» и «имеет» компромисс. Иногда это проще и имеет смысл наследовать непосредственно из суперкласса. В других случаях имеет смысл создать отдельный класс и включить класс, который вы унаследовали бы от переменной-члена. Вы по-прежнему можете получить доступ к функциям класса, но не привязаны к интерфейсу или каким-либо другим ограничениям, которые могут возникнуть в результате наследования класса.

Что вы делаете? Как и во многих вещах ... это зависит от контекста. Руководство, которое я хотел бы использовать, состоит в том, что для наследования другого класса действительно должно быть отношение «есть». Так что, если вы пишете класс под названием BMW, он может унаследовать автомобиль, потому что BMW поистине автомобиль. Класс лошади может наследовать от класса млекопитающих, потому что лошадь на самом деле является млекопитающим в реальной жизни, и любая функциональность Млекопитающих должна относиться к Лошади. Но можете ли вы сказать, что команда - это список? Из того, что я могу сказать, это не похоже, что команда действительно «является» списком. Поэтому в этом случае у меня был бы List как переменная-член.

9
ответ дан Paul J Abernathy 15 August 2018 в 18:36
поделиться

Мой грязный секрет: мне все равно, что говорят люди, и я это делаю. .NET Framework распространяется с помощью «XxxxCollection» (UIElementCollection для примера моей главы).

Итак, что мешает мне говорить:

team.Players.ByName("Nicolas")

Когда я нахожу это лучше, чем

team.ByName("Nicolas")

Кроме того, мой PlayerCollection может использоваться другим классом, например «Club», без дублирования кода.

club.Players.ByName("Nicolas")

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

team.Players.ByName("Nicolas") 

или

team.ByName("Nicolas")

Действительно. У вас есть какие-то сомнения? Теперь, возможно, вам нужно играть с другими техническими ограничениями, которые не позволяют использовать List & lt; T> в вашем реальном прецеденте. Но не добавляйте ограничение, которое не должно существовать. Если Microsoft не документировала причину, то это, безусловно, «лучшая практика», идущая из ниоткуда.

81
ответ дан Peter Mortensen 15 August 2018 в 18:36
поделиться
  • 1
    Расширяясь на # 2, список List не имеет смысла для have-a . – Cruncher 12 February 2014 в 15:47
  • 2
    Хотя важно иметь мужество и инициативу, чтобы оспаривать принятую мудрость, когда это уместно, я думаю, что разумно сначала понять, почему принятая мудрость стала принята в первую очередь, прежде чем приступать к ее оспариванию. -1, потому что «Игнорировать это правило!» не является хорошим ответом на «Почему это руководство существует?». Кстати, сообщество не просто «обвинило меня». в этом случае, но предоставил удовлетворительное объяснение, которое удалось убедить меня. – Superbest 12 February 2014 в 15:59
  • 3
    На самом деле, когда никаких объяснений не делается, ваш единственный путь - это толкать границу и тестировать, не боясь нарушения. Теперь. Спросите себя, даже если List & lt; T & gt; это была не очень хорошая идея. Насколько больно было бы изменить наследование из List & lt; T & gt; к коллекции & lt; T & gt; ? Догадка: меньше времени, чем публикация на форуме, независимо от длины вашего кода с инструментами рефакторинга. Теперь это хороший вопрос, но не практический. – Nicolas Dorier 12 February 2014 в 16:02
  • 4
    Чтобы уточнить: я, конечно, не жду от благословения SO, чтобы унаследовать все, что захочу, но хочу этого, но суть моего вопроса заключалась в том, чтобы понять соображения, которые должны входить в это решение, и почему так много опытных программистов, похоже, решил противоположное тому, что я сделал. Collection<T> не совпадает с List<T>, поэтому для крупномасштабного проекта может потребоваться довольно много работы. – Superbest 12 February 2014 в 16:31
  • 5
    К сожалению, SuperBest, я не подразумевал, что ваш вопрос бесполезен, а также то, что вы слишком полагаетесь на сообщество. Это хороший вопрос или не надо было бы давать ответ. Я хочу сказать, что кто-то говорит: «Не делай этого». и не объясняет, почему, вы должны игнорировать совет, рассматривая влияние на то, что произойдет, если вы ошибаетесь. Изменение списка & lt; T & gt; к коллекции & lt; T & gt; это не проблема с сегодняшними инструментами и шаблонами рефакторинга. (даже для большого проекта) Как вы сказали, List & lt; T & gt; имеют некоторое преимущество в коллекции & lt; T & gt;, текущий принятый ответ является философским аргументом, а не практическим. – Nicolas Dorier 12 February 2014 в 23:47
  • 6
    team.ByName("Nicolas") означает "Nicolas" имя команды. – Sam Leach 14 February 2014 в 22:54
  • 7
    Это хороший момент - можно подумать о команде как о простом имени. Однако, если мое приложение нацелено на работу с действиями игроков, то это мышление немного менее очевидно. Во всяком случае, кажется, что проблема заканчивается композицией против наследования в конце. – Superbest 16 February 2014 в 22:34
  • 8
    Это один достойный способ взглянуть на это. В боковой заметке, пожалуйста, рассмотрите это: человек богатства владеет несколькими футбольными командами, он дает друг другу в подарок, друг меняет имя команды, стреляет в тренера, и заменяет всех игроков. Команда друзей встречает мужскую команду на зеленом поле, и когда мужская команда проигрывает, он говорит: «Я не могу поверить, что ты избиваешь меня с командой, которую я тебе дал!» Человек прав? как бы вы это проверить? – user1852503 17 February 2014 в 07:59
  • 9
    Во-первых, вопрос не имеет никакого отношения к составу и наследованию. Ответ: OP не хочет реализовывать более конкретный вид списка, поэтому он не должен расширять List & lt; gt ;. Я извращен, потому что у вас очень высокие оценки в stackoverflow, и я должен это четко понимать, и люди доверяют тому, что вы говорите, поэтому на данный момент минутка 55 человек, которые поддержали и чья идея запуталась, верят, что любой способ или другое нормально строить система, но это явно не так! нет # не-злоба – sam 9 August 2016 в 13:16
  • 10
    @sam Он хочет, чтобы поведение списка. У него есть два варианта: он может расширять список (наследование), или он может включать список внутри своего объекта (композиции). Возможно, вы неправильно поняли часть вопроса или ответа, а не 55 человек, которые ошибаются, и вы правы? :) – Tim B 9 August 2016 в 14:24
  • 11
    Да. Это вырожденный случай с одним супер и суб, но я даю более общее объяснение его конкретному вопросу. У него может быть только одна команда, и у нее всегда может быть один список, но выбор по-прежнему должен наследовать список или включать его в качестве внутреннего объекта. Когда вы начинаете включать несколько типов команд (футбол, крикет и т. Д.), И они начинают больше, чем просто список игроков, вы видите, как у вас есть вопрос о полном составе и наследовании. Глядя на общую картину, важно в таких случаях, чтобы избежать неправильного выбора на раннем этапе, а затем много рефакторинга. – Tim B 10 August 2016 в 08:55
  • 12
    ОП не запрашивал композицию, но прецедент является четким примером проблемы X: Y, из которой один из 4 принципов объектно-ориентированного программирования сломан, неправильно использован или не полностью понят. Более ясный ответ - либо написать специализированную коллекцию, как Stack или Queue (которая, как представляется, не подходит для использования), или для понимания композиции. Футбольная команда НЕ является списком футболистов. Независимо от того, спросил ли он ОП прямо или нет, не имеет значения, не понимая абстракции, инкапсуляции, наследования и полиморфизма, они не поймут ответ, ergo, X: Y amok – J.T. McGuigan 1 February 2018 в 18:42

Что говорится в рекомендациях, так это то, что публичный API не должен раскрывать внутреннее дизайнерское решение о том, используете ли вы список, набор, словарь, дерево или что-то еще. «Команда» не обязательно является списком. Вы можете реализовать его как список, но пользователи вашего общедоступного API должны использовать ваш класс в необходимости знать основы. Это позволяет вам изменить свое решение и использовать другую структуру данных, не затрагивая открытый интерфейс.

7
ответ дан QuentinUK 15 August 2018 в 18:36
поделиться
  • 1
    В ретроспективе, после объяснения @EricLippert и других, вы действительно дали отличный ответ для части API моего вопроса - помимо того, что вы сказали, если я сделаю class FootballTeam : List<FootballPlayers>, пользователи моего класса смогут скажите, что я унаследовал от List<T>, используя рефлексию, увидев методы List<T>, которые не имеют смысла для футбольной команды, и возможность использовать FootballTeam в List<T>, поэтому я бы был показывая детали реализации клиенту (без необходимости). – Superbest 18 February 2014 в 19:19

Футбольная команда не является списком футболистов. Футбольная команда состоит из списка футболистов!

Это логически неверно:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

, и это верно:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}
23
ответ дан sam 15 August 2018 в 18:36
поделиться
  • 1
    Это не объясняет почему . OP знает, что большинство других программистов чувствуют себя так, он просто не понимает, почему это важно. Это действительно только предоставление информации уже в вопросе. – Servy 11 February 2014 в 18:48
  • 2
    Это первое, что я думал о том, как его данные были просто моделированы неправильно. Поэтому причина в том, что ваша модель данных неверна. – rball 11 February 2014 в 19:11
  • 3
    Почему бы не наследовать из списка? Потому что он не хочет более конкретного типа списка. – sam 13 February 2014 в 15:36
  • 4
    Я не думаю, что второй пример правильный. Вы подвергаете себя состоянию и тем самым нарушаете инкапсуляцию. Состояние должно быть скрыто / внутренним для объекта, а способ его изменения должен осуществляться с помощью общедоступных методов. Точно, какие методы должны быть определены из бизнес-требований, но они должны быть «нуждаться в этом прямо сейчас» -базис, а не «может быть удобен некоторое время, когда я не знаю». Держите свою апи тубу, и вы сэкономите время и силы. – kai 15 September 2015 в 15:28
  • 5
    Инкапсуляция - это не вопрос. Я сосредоточен на том, чтобы не расширять тип, если вы не хотите, чтобы этот тип вел себя более определенным образом. Эти поля должны быть autoproperties в c # и get / set методами на других языках, но здесь, в этом контексте, это совершенно лишнее – sam 16 September 2015 в 07:46

Что делать, если FootballTeam имеет команду резервов вместе с основной командой?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

Как бы вы это моделировали?

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

Очевидно, что отношение имеет , а не является .

или RetiredPlayers?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

Как правило, если вы когда-либо захотите наследовать из коллекции, назовите класс SomethingCollection.

Семантически ли ваш SomethingCollection имеет смысл? Выполняйте это только в том случае, если ваш тип является коллекцией Something.

В случае FootballTeam это звучит неправильно. A Team больше, чем Collection. В Team могут быть тренеры, тренеры и т. Д., Как указывали другие ответы.

FootballCollection звучит как сборник футбольных мячей или, может быть, коллекция футбольных принадлежностей. TeamCollection, сборник команд.

FootballPlayerCollection звучит как коллекция игроков, которая будет действительным именем для класса, наследующего от List<FootballPlayer>, если вы действительно этого хотели.

Действительно List<FootballPlayer> - совершенно хороший тип, с которым нужно иметь дело. Возможно, IList<FootballPlayer>, если вы возвращаете его из метода.

В резюме

Задайте вопрос

  1. Is X a Y? или Имеет X a Y?
  2. Знают ли мои имена классов, каковы они?
35
ответ дан Sam Leach 15 August 2018 в 18:36
поделиться
  • 1
    Идеальный пример, чтобы проиллюстрировать, почему идея OP была плохая. +1 – nawfal 29 May 2014 в 07:22
  • 2
    Если тип каждого игрока классифицирует его как принадлежащий либо DefensivePlayers, OffensivePlayers, либо OtherPlayers, может быть законно полезно иметь тип, который может использоваться кодом, который ожидает List<Player>, но также включенным членов DefensivePlayers, OffsensivePlayers или SpecialPlayers типа IList<DefensivePlayer>, IList<OffensivePlayer> и IList<Player>. Можно использовать отдельный объект для кеширования отдельных списков, но инкапсулировать их в том же самом объекте, что и основной список, выглядят более чистыми [используйте недействительность перечислителя списка ... – supercat 11 September 2014 в 21:48
  • 3
    ... как сигнал к тому, что основной список изменился, и подэлементы должны быть восстановлены при следующем доступе]. – supercat 11 September 2014 в 21:48
  • 4
    Хотя я согласен с точкой, это действительно разрывает мою душу, чтобы увидеть, что кто-то дает советы по дизайну и предлагает разоблачить конкретный список & lt; T & gt; с публичным геттером и сеттером в бизнес-объекте :( – kai 1 February 2016 в 10:56

Как все отметили, команда игроков не является списком игроков. Эта ошибка совершается многими людьми во всем мире, возможно, на разных уровнях знаний. Часто проблема тонкая, а иногда и очень грубая, как в этом случае. Такие проекты плохи, потому что они нарушают Принцип замещения Лискова. В Интернете есть много хороших статей, объясняющих эту концепцию, например http://en.wikipedia.org/wiki/Liskov_substitution_principle

Итак, есть два правила, которые должны быть сохранены в отношениях между родителями и детьми между классами:

  • a Ребенок не должен иметь никакой характеристики, которая меньше, чем то, что полностью определяет родителя.
  • Родитель должен не требовать характерный в дополнение к тому, что полностью определяет ребенка.

Другими словами, родитель является необходимым определением для ребенка, а ребенок является достаточным определением родителя.

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

  • Является ли футбольная команда списком футболистов? (Все ли свойства списка относятся к команде в том же значении) Является ли команда коллекцией однородных объектов? Да, команда - это сборник игроков. Является ли порядок включения игроков в описание состояния команды и обеспечивает ли команда сохранение последовательности, если она явно не изменена? Нет, и Нет. Ожидается, что игроки будут включены / сброшены на основе их последовательной позиции в команде? Нет

Как вы видите, только первая характеристика списка применима к команде. Следовательно, команда не является списком. Список будет деталью реализации того, как вы управляете своей командой, поэтому ее следует использовать только для хранения объектов игрока и манипулировать с помощью методов класса Team.

На этом этапе я хотел бы отметить что класс Team должен, на мой взгляд, даже не реализовываться с использованием List; в большинстве случаев он должен быть реализован с использованием структуры данных Set (например, HashSet).

43
ответ дан Satyan Raina 15 August 2018 в 18:36
поделиться
  • 1
    Хороший улов в списке против набора. Это, кажется, ошибка, которая слишком часто делается. Когда может быть только один экземпляр элемента в коллекции, какой-то набор или словарь должны быть предпочтительным кандидатом для реализации. Хорошо иметь команду с двумя игроками с тем же именем. Это не нормально, если один игрок включал дважды в одно и то же время. – Fred Mitchell 11 February 2014 в 23:26
  • 2
    +1 для некоторых хороших точек, хотя более важно спросить «может ли структура данных разумно поддерживать все полезное (в идеале короткое и долгосрочное)», а не «делает ли структура данных более полезной». – Tony Delroy 17 February 2014 в 12:26
  • 3
    @TonyD Ну, точки, которые я поднимаю, - это не то, что нужно проверить & quot; если структура данных делает больше, чем то, что полезно ". Необходимо проверить & quot; Если структура родительских данных делает то, что не имеет значения, бессмысленно или противоречит интуитивному поведению, которое может подразумевать класс Child. & Quot ;. – Satyan Raina 17 February 2014 в 13:07
  • 4
    @SatyanRaina: и у них нет ничего плохого в том, что вы можете делать лишние вещи, которые не имеют значения или бессмысленны, если они на самом деле не компрометируют операции, которые имеют смысл и будут использоваться. Например, если сбалансированное двоичное дерево происходит с итерацией элементов в порядке сортировки по ключам, и это необязательно, это тоже не проблема. Например. «порядок включения игроков [описывающих состояние команды»: если не требуется какой-либо заказ, или, вероятно, желаемый многопользовательский порядок может быть эффективно удобно сформирован, то конкретный заказ по умолчанию не наносит вреда. – Tony Delroy 17 February 2014 в 13:54
  • 5
    @TonyD На самом деле существует проблема с наличием нерелевантных характеристик, полученных от родителя, поскольку во многих случаях это может привести к отрицательным результатам. Программист мог бы расширить Human {eat (); бег(); write ();} из гориллы {eat (); бег(); swing ();} думая, что нет ничего плохого в человеке с дополнительной особенностью возможности качать. И затем в игровом мире ваш человек внезапно начинает обходить все предполагаемые наземные препятствия, просто размахивая деревьями. Если явно не указано, практический Человек не должен быть способен качаться. Такой дизайн оставляет api очень открытым для злоупотреблений и запутывания – Satyan Raina 17 February 2014 в 16:51

Когда они говорят, что List<T> «оптимизирован», я думаю, они хотят иметь в виду, что у него нет таких функций, как виртуальные методы, которые стоят дороже. Таким образом, проблема заключается в том, что после того, как вы откроете List<T> в своем общедоступном API , вы потеряете способность принудительно применять бизнес-правила или настраивать свои функции позже. Но если вы используете этот унаследованный класс как внутренний в своем проекте (в отличие от потенциально подверженного тысячам ваших клиентов / партнеров / других команд как API), тогда это может быть ОК, если это экономит ваше время, и это функциональность, которую вы хотите дублировать. Преимущество наследования с List<T> заключается в том, что вы устраняете много немого кода обертки, который никогда не будет настроен в обозримом будущем. Кроме того, если вы хотите, чтобы ваш класс явно имел ту же семантику, что и List<T> для жизни ваших API, также может быть и в порядке.

Я часто вижу, что многие люди делают тонны дополнительной работы только из-за Правило FxCop говорит так, или чей-то блог говорит, что это «плохая» практика. Много раз, это превращает код в дизайн палодовости паттерн. Как и в случае с множеством рекомендаций, относитесь к нему как к руководящему принципу, который может иметь исключения.

5
ответ дан ShitalShah 15 August 2018 в 18:36
поделиться

Наконец, некоторые предлагают обернуть список в чем-то:

Это правильный путь. «Безусловно многословие» - это плохой способ взглянуть на это. Он имеет явное значение, когда вы пишете my_team.Players.Count. Вы хотите подсчитать игроков.

my_team.Count

.. ничего не значит. Считайте что?

Команда не является списком - состоят не только из списка игроков.

Если вы действительно обеспокоены тем, что он слишком многословный, вы всегда можете выставлять свойства из команда:

public int PlayerCount {
    get {
        return Players.Count;
    }
}

.. которая становится:

my_team.PlayerCount

Теперь это имеет смысл и придерживается Закона Деметры .

Вы также должны рассмотреть возможность соблюдения принципа повторного использования Composite . Наследуя от List<T>, вы говорите, что команда - это список игроков и выставляя из нее ненужные методы. Это неверно. Как вы сказали, команда - это больше, чем список игроков: у нее есть имя, менеджеры, члены совета, тренеры, медицинский персонал, зарплата и т. Д. Если ваш класс команды содержит список игроков, вы «У команды есть список игроков», но у нее также могут быть и другие вещи.

234
ответ дан Simon Whitehead 15 August 2018 в 18:36
поделиться
  • 1
    Я изменил свою формулировку на «подробный». чтобы сделать его более ясным. Я не понимаю, почему вы говорите, что my_team.Count «ничего не значит». Для меня «подсчет команды» является совершенно смысловой концепцией. Я понимаю, почему вы можете настаивать на том, что на самом деле «учитываются игроки команды». - так это философское различие? Или есть практическая причина, почему упаковка лучше? Я жалуюсь на дополнительную работу, потому что каждый раз, когда я вернусь к этому коду, я подумаю: «Подождите, зачем я это обернул? почему бы просто не наследовать? & quot; и я не мог придумать комментарий, объясняющий это. – Superbest 11 February 2014 в 05:11
  • 2
    @Superbeast Если бы я сказал вам: «Я присоединился к команде X сегодня». Вы предположили бы, что я игрок? Или попросите разъяснить мою роль в команде? – Simon Whitehead 11 February 2014 в 05:26
  • 3
    Я не хочу сказать, что это был игрок. Более того, я бы ожидал, что вы прямо укажете это, если вы присоединились к другой емкости. – Superbest 11 February 2014 в 07:07
  • 4
    Вообще говоря, вы должны избегать наследования, когда это возможно. Частью причины является то, что в .net мы можем наследовать только один класс. Итак, как только вы наследуете List, вы не можете наследовать что-то еще. Поэтому, если, например, позже вы хотите использовать ObservableCollection (для привязки данных), вы введете нарушение. – Aron 11 February 2014 в 07:24
  • 5
    @SimonWhitehead Я не согласен с вашим советом, чтобы посмотреть на "software-architecure POV". Результат семантического анализа в проблемном пространстве должен быть полностью агностическим для ограничений компьютерного пространства. Для меня первоначальная проблема - неправильный семантический анализ: «Джонни присоединился к команде X», не означает, что команда - это список игроков. – franssu 11 February 2014 в 11:41

Это зависит от контекста

. Когда вы рассматриваете свою команду как список игроков, вы проецируете «идею» команды шара ноги на один аспект: вы сокращаете «команду» до людей, которых вы видите на поле. Эта проекция верна только в определенном контексте. В другом контексте это может быть совершенно неправильно. Представьте, что вы хотите стать спонсором команды. Поэтому вам нужно поговорить с менеджерами команды. В этом контексте команда прогнозируется в списке своих менеджеров. И эти два списка обычно не перекрываются очень сильно. Другие контексты - текущие и прежние игроки и т. Д.

Нечеткая семантика

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

Классы расширяемы

Когда вы используете класс только с одним членом (например, IList activePlayers), вы можете использовать имя участника (и, кроме того, его комментарий), чтобы сделать контекст понятным. Когда есть дополнительные контексты, вы просто добавляете дополнительный член.

Классы сложнее

В некоторых случаях может быть излишним создание дополнительного класса. Каждое определение класса должно быть загружено через загрузчик классов и будет кэшироваться виртуальной машиной. Это требует производительности и памяти во время выполнения. Когда у вас есть очень специфический контекст, может быть, стоит подумать о футбольной команде как о списке игроков. Но в этом случае вы действительно должны использовать только IList , а не класс, полученный из него.

Заключение / соображения

Когда у вас очень специфический контекст, все в порядке рассматривать команду как список игроков. Например, внутри метода вполне нормально писать

IList<Player> footballTeam = ...

При использовании F # может даже быть ОК, чтобы создать аббревиатуру типа

type FootballTeam = IList<Player>

. Но когда контекст более широкий или даже неясный, вы не должны этого делать. Это особенно важно, когда вы создаете новый класс, где неясно, в каком контексте он может использоваться в будущем. Предупреждающий знак - это когда вы начинаете добавлять дополнительные атрибуты в свой класс (имя команды, тренера и т. Д.). Это явный признак того, что контекст, в котором будет использоваться класс, не является фиксированным и изменится в будущем. В этом случае вы не можете рассматривать команду как список игроков, но вы должны моделировать список игроков (в настоящее время активных, а не травмированных и т. Д.) В качестве атрибута команды.

19
ответ дан stefan.schwetschke 15 August 2018 в 18:36
поделиться

Это зависит от поведения вашего «командного» объекта. Если он ведет себя точно так же, как в коллекции, возможно, это будет нормально представлять его сначала с помощью простого списка. Тогда вы можете начать замечать, что вы продолжаете дублировать код, который повторяется в списке; на данный момент у вас есть возможность создать объект FootballTeam, который обертывает список игроков. Класс FootballTeam становится домом для всего кода, который повторяется в списке игроков.

Это делает мой код излишне подробным. Теперь я должен назвать my_team.Players.Count вместо my_team.Count. К счастью, с C # я могу определить индексаторов, чтобы сделать индексирование прозрачным, и переслать все методы внутреннего списка ... Но это много кода! Что я могу получить за все это?

Инкапсуляция. Ваши клиенты не должны знать, что происходит внутри FootballTeam. Для всех ваших клиентов это может быть реализовано путем просмотра списка игроков в базе данных. Им не нужно знать, и это улучшает ваш дизайн.

Это просто не имеет никакого смысла. Футбольная команда не имеет «списка» игроков. Это список игроков. Вы не говорите: «Джон Макфуталлер присоединился к игрокам SomeTeam». Вы говорите: «Джон присоединился к SomeTeam». Вы не добавляете письмо в «символы строки», вы добавляете письмо к строке. Вы не добавляете книгу в книги библиотеки, вы добавляете книгу в библиотеку.

Точно :) вы скажете footballTeam.Add (john), а не footballTeam.List.Add (Джон). Внутренний список не будет виден.

12
ответ дан xpmatteo 15 August 2018 в 18:36
поделиться
  • 1
    OP хочет понять, как MODEL REALITY, но затем определяет футбольную команду как список футболистов (что неверно концептуально). Это проблема. По моему мнению, любой другой аргумент вводит в заблуждение. – sam 22 December 2014 в 09:32
  • 2
    Я не согласен с тем, что список игроков ошибочен концептуально. Не обязательно. Как кто-то еще написал на этой странице, «Все модели ошибочны, но некоторые из них полезны». – xpmatteo 23 December 2014 в 13:20
  • 3
    Правильные модели трудно получить, после их выполнения они полезны. Если модель может быть использована неправильно, она ошибочна концептуально. stackoverflow.com/a/21706411/711061 – sam 23 December 2014 в 13:49

Позвольте мне переписать ваш вопрос. поэтому вы можете увидеть тему с другой точки зрения.

Когда мне нужно представлять футбольную команду, я понимаю, что это в основном имя. Например: «The Eagles»

string team = new string();

Затем я понял, что в командах также есть игроки.

Почему я не могу просто расширить строковый тип, чтобы он также содержал список игроков?

Ваша точка входа в проблему произвольна. Попытайтесь подумать, что делает команда (свойства), а не то, что она .

После этого вы можете увидеть, разделяет ли он свойства с другими классами. И подумайте о наследовании.

81
ответ дан Peter Mortensen 5 September 2018 в 17:48
поделиться

Предпочитают интерфейсы над классами

Классы должны избегать получения классов и вместо этого применять минимальные интерфейсы.

Inheritance breaks Encapsulation

Вывод из классов прерывает инкапсуляцию :

  • раскрывает внутренние подробности о том, как реализована ваша коллекция
  • ] объявляет интерфейс (набор публичных функций и свойств), который может быть неприемлем

. Среди прочего это затрудняет реорганизацию вашего кода.

Классы являются частью реализации

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

Концептуально тот факт, что тип данных System.List называется «списком», является немного красношейникой. A System.List<T> - измененная упорядоченная коллекция, которая поддерживает амортизированные операции O (1) для добавления, вставки и удаления элементов и операций O (1) для извлечения числа элементов или получения и установки элемента по индексу.

Чем меньше Интерфейс, тем гибче код

При проектировании структуры данных, чем проще интерфейс, тем более гибким является код. Просто посмотрите, как мощный LINQ для демонстрации этого.

Как выбрать интерфейсы

Когда вы думаете «список», вы должны начать с самого себя: «Мне нужно представлять коллекцию бейсболистов». Итак, допустим, вы решили моделировать это с помощью класса. Сначала вы должны решить, какое минимальное количество интерфейсов, которое этот класс должен будет предъявить.

Некоторые вопросы, которые могут помочь в руководстве этим процессом:

  • Нужно ли иметь счет? Если не рассмотреть реализацию IEnumerable<T>
  • Будет ли эта коллекция изменяться после ее инициализации? Если не считать IReadonlyList<T> .
  • Важно ли, чтобы я мог обращаться к элементам по индексу? Рассмотрим ICollection<T>
  • Является ли порядок, в котором я добавляю элементы в сборник, важно? Может быть, это ISet<T> ?
  • Если вы действительно хотите эту вещь, тогда идите и реализуйте IList<T> .

Таким образом, вы не будете связывать другие части кода с деталями реализации вашей коллекции бейсболистов и сможете свободно изменять, как это реализовано, если вы уважаете интерфейс.

Используя этот подход, вы обнаружите, что код становится легче читать, рефакторировать и повторно использовать.

Заметки об исключении котельной

Реализация интерфейсов в современной среде IDE должна быть простой. Щелкните правой кнопкой мыши и выберите «Использовать интерфейс». Затем переместите все реализации в класс-член, если вам нужно.

Тем не менее, если вы обнаружите, что пишете много шаблонов, это потенциально потому, что вы подвергаете больше функций, чем вам должно быть. Это то же самое, что вы не должны наследовать от класса.

Вы также можете разрабатывать меньшие интерфейсы, которые имеют смысл для вашего приложения, и, возможно, всего лишь несколько вспомогательных функций расширения для сопоставления этих интерфейсов с любыми другими, которые вам нужны. Это подход, который я использовал в своем собственном интерфейсе IArray для библиотеки LinqArray .

0
ответ дан cdiggins 29 October 2018 в 01:56
поделиться

Это классический пример состава против наследования .

В этом конкретном случае:

Является ли команда списком игроков с добавленным поведением

или

Является ли команда собственностью, которая, как оказалось, содержит список игроков.

Расширяя список, вы ограничиваете себя несколькими способами:

  1. Вы не можете ограничить доступ (например, остановить людей, изменяющих список). Вы получаете все методы List, хотите ли вы / хотите их всех или нет.

  2. Что произойдет, если вы хотите иметь списки других вещей. Например, в командах есть тренеры, менеджеры, болельщики, оборудование и т. Д. Некоторые из них вполне могут быть списками в своем собственном праве.

  3. Вы ограничиваете свои варианты наследования. Например, вы можете создать общий объект Team, а затем получить BaseballTeam, FootballTeam и т. Д. Чтобы наследовать из списка, вам нужно наследовать от Team, но это означает, что все команды разных типов вынуждены иметь одну и ту же реализацию этого списка.

Состав - включая объект, дающий поведение, которое вы хотите в своем объекте.

Наследование - ваш объект становится экземпляром объекта, у которого есть поведение, которое вы хотите.

Оба имеют свои применения, но это ясный случай, когда композиция предпочтительнее.

85
ответ дан Peter Mortensen 29 October 2018 в 01:56
поделиться
  • 1
    Расширяясь на # 2, список List не имеет смысла для have-a . – Cruncher 12 February 2014 в 15:47
  • 2
    Во-первых, вопрос не имеет никакого отношения к составу и наследованию. Ответ: OP не хочет реализовывать более конкретный вид списка, поэтому он не должен расширять List & lt; gt ;. Я извращен, потому что у вас очень высокие оценки в stackoverflow, и я должен это четко понимать, и люди доверяют тому, что вы говорите, поэтому на данный момент минутка 55 человек, которые поддержали и чья идея запуталась, верят, что любой способ или другое нормально строить система, но это явно не так! нет # не-злоба – sam 9 August 2016 в 13:16
  • 3
    @sam Он хочет, чтобы поведение списка. У него есть два варианта: он может расширять список (наследование), или он может включать список внутри своего объекта (композиции). Возможно, вы неправильно поняли часть вопроса или ответа, а не 55 человек, которые ошибаются, и вы правы? :) – Tim B 9 August 2016 в 14:24
  • 4
    Да. Это вырожденный случай с одним супер и суб, но я даю более общее объяснение его конкретному вопросу. У него может быть только одна команда, и у нее всегда может быть один список, но выбор по-прежнему должен наследовать список или включать его в качестве внутреннего объекта. Когда вы начинаете включать несколько типов команд (футбол, крикет и т. Д.), И они начинают больше, чем просто список игроков, вы видите, как у вас есть вопрос о полном составе и наследовании. Глядя на общую картину, важно в таких случаях, чтобы избежать неправильного выбора на раннем этапе, а затем много рефакторинга. – Tim B 10 August 2016 в 08:55
  • 5
    ОП не запрашивал композицию, но прецедент является четким примером проблемы X: Y, из которой один из 4 принципов объектно-ориентированного программирования сломан, неправильно использован или не полностью понят. Более ясный ответ - либо написать специализированную коллекцию, как Stack или Queue (которая, как представляется, не подходит для использования), или для понимания композиции. Футбольная команда НЕ является списком футболистов. Независимо от того, спросил ли он ОП прямо или нет, не имеет значения, не понимая абстракции, инкапсуляции, наследования и полиморфизма, они не поймут ответ, ergo, X: Y amok – J.T. McGuigan 1 February 2018 в 18:42
Другие вопросы по тегам:

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