В java все ссылается, поэтому, когда у вас есть что-то вроде: Point pnt1 = new Point(0,0);
Java делает следующее:
[/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 с двумя различными ссылками , [/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
) [/g9]
В методе tricky
:
arg1.x = 100;
arg1.y = 100;
[/g10]
Далее в методе tricky
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
Здесь вы сначала создаете новую ссылку temp
Point, которая будет point в том же месте, что и arg1
справка. Затем вы перемещаете ссылку arg1
в точку в то же место, что и arg2
. Наконец, arg2
будет point в том же месте, что и temp
.
[/g11]
Отсюда область действия tricky
метод ушел, и вы больше не имеете доступа к ссылкам: arg1
, arg2
, temp
. Но важно отметить, что все, что вы делаете с этими ссылками, когда они «в жизни», будет постоянно влиять на объект, на который они указывают .
Итак, после выполнения метода tricky
, когда вы вернетесь к main
, у вас есть такая ситуация: [/g12]
Итак, теперь полное выполнение программы будет be:
X1: 0 Y1: 0
X2: 0 Y2: 0
X1: 100 Y1: 100
X2: 0 Y2: 0
Здесь есть хорошие ответы. Я бы добавил к ним следующие моменты.
Каков правильный способ 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 год?» или «Какой игрок в Денвере, который ранее играл за другую команду, имел наибольшее увеличение по годам в ярдах?» или « В этом году все пигеры прошли весь путь? »
То есть, футбольная команда кажется мне хорошо смоделированной как сборник исторических фактов , например, когда игрок был завербован, получил травму, вышел на пенсию и т. д. Очевидно, что текущий список игроков является важным фактом, который, вероятно, должен быть фронт-центром, но могут быть другие интересные вещи, которые вы хотите сделать с этим объектом которые требуют более исторической перспективы.
Я просто хотел добавить, что Бертран Мейер, изобретатель Эйфеля и дизайн по контракту, наследовал бы Team
от List<Player>
, не так как моргнув веком.
В своей книге, Объектно-ориентированное программное обеспечение , он обсуждает реализацию GUI-системы, в которой прямоугольные окна могут иметь дочерние окна. Он просто имеет Window
наследовать от Rectangle
и Tree<Window>
для повторного использования реализации.
Однако C # не является Eiffel. Последний поддерживает множественное наследование и переименование функций . В C #, когда вы выполняете подкласс, вы наследуете интерфейс и реализацию. Вы можете переопределить реализацию, но соглашения о вызовах копируются непосредственно из суперкласса. Однако в Eiffel вы можете изменить имена общедоступных методов, поэтому вы можете переименовать Add
и Remove
в Hire
и Fire
в свой Team
. Если экземпляр Team
снова возвращается к List<Player>
, вызывающий будет использовать Add
и Remove
для его изменения, но ваши виртуальные методы Hire
и Fire
будут вызваны.
Каков правильный способ C # для представления структуры данных ...
Remeber: «Все модели ошибочны, но некоторые из них полезны». - George EP Box
Нет «правильного пути», только полезный.
Выберите тот, который вам полезен и / ваши пользователи , Вот и все. Развивайте экономически, не переучивайте. Чем меньше кода вы пишете, тем меньше кода вам нужно будет отлаживать. (читайте следующие выпуски).
- Отредактировано
. Мой лучший ответ был бы ... это зависит. Наследование из списка приведет к тому, что клиенты этого класса будут подвергнуты методам, которые могут не отображаться, прежде всего потому, что FootballTeam выглядит как бизнес-объект.
- Издание 2
Я искренне не помню, что я имел в виду в комментарии «не переучивайтесь». Хотя я считаю, что мышление KISS является хорошим ориентиром, я хочу подчеркнуть, что наследование бизнес-класса из списка создаст больше проблем, чем устраняет, из-за утечки абстракции .
С другой стороны, я считаю, что существует ограниченное число случаев, когда просто наследовать из Списка полезно. Как я писал в предыдущем издании, все зависит. Ответ на каждый случай сильно зависит от знания, опыта и личных предпочтений.
Спасибо @kai за то, что он помог мне более точно рассказать о ответе.
List
, будет подвергать клиентов этого класса методам, которые могут не подвергаться экспонату i> & quot; именно то, что делает наследование от List
абсолютным no-no. После разоблачения они постепенно становятся злоупотребляемыми и неправильно используемыми с течением времени. Экономия времени в настоящее время путем «экономичного развития» может легко привести к десятикратному сокращению сбережений в будущем: отладка злоупотреблений и, в конечном счете, рефакторинг для исправления наследования. Принцип YAGNI также можно представить как значение: вам не нужны все эти методы из List
, поэтому не подвергайте их воздействию.
– Craig Young
15 February 2014 в 20:59
List
, потому что это быстро и просто и, следовательно, приведет к уменьшению количества ошибок, это просто неправильно, это просто плохой дизайн и плохой дизайн приводит к гораздо большему количеству ошибок, чем «над разработкой».
– kai
15 September 2015 в 15:40
Ничего себе, ваш пост имеет целую массу вопросов и вопросов. Большинство аргументов, которые вы получаете от 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>
позволяет использовать элементы (например, 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.
}
List<T>
расширен как частный внутренний класс, который использует обобщения для упрощения использования и объявления List<T>
. Если расширение было всего лишь class FootballRoster : List<FootballPlayer> { }
, то да, я мог бы использовать псевдоним вместо этого. Я предполагаю, что стоит отметить, что вы можете добавить дополнительных членов / функциональность, но она ограничена, так как вы не можете переопределить важных членов List<T>
.
– m-y
11 February 2014 в 18:33
Collection<T> allows for members (i.e. Add, Remove, etc.) to be overridden
Теперь , что является хорошей причиной не наследовать от List. Остальные ответы имеют слишком много философии.
– Navin
12 February 2014 в 05:54
Здесь есть много отличных ответов, но я хочу затронуть то, о чем я не упоминал: Объектно-ориентированный дизайн посвящен полномочным объектам .
Вы хотите инкапсулировать все свои правила, дополнительную работу и внутренние детали внутри соответствующего объекта. Таким образом, другим объектам, взаимодействующим с этим, не нужно беспокоиться обо всем этом. На самом деле, вы хотите пойти дальше и активно предотвратить другие объекты от обхода этих внутренних элементов.
Когда вы наследуете List
, все остальные объекты могут видеть вас как Список. Они имеют прямой доступ к методам добавления и удаления игроков. И ты потеряешь контроль; например:
Предположим, вы хотите различать, когда игрок уходит, зная, удалены ли они, ушли или уволены. Вы можете реализовать метод RemovePlayer
, который принимает соответствующее перечисление ввода. Однако, наследуя от List
, вы не сможете предотвратить прямой доступ к Remove
, RemoveAll
и даже Clear
. В результате вы фактически лишили вашего класса FootballTeam
.
Дополнительные мысли об инкапсуляции ... Вы подняли следующее беспокойство:
< blockquote>Это делает мой код ненужным подробным. Теперь я должен вызывать my_team.Players.Count, а не только my_team.Count.
blockquote> Вы правы, это было бы излишне подробным для всех клиентов, чтобы использовать вашу команду. Тем не менее, эта проблема очень мала по сравнению с тем фактом, что вы выставили List Players
всем и каждому, чтобы они могли играть с вашей командой без вашего согласия.
Вы продолжаете говорить:
Это просто не имеет никакого смысла. Футбольная команда не имеет «списка» игроков. Это список игроков. Вы не говорите: «Джон Макфуталлер присоединился к игрокам SomeTeam». Вы говорите: «Джон присоединился к SomeTeam».
blockquote>Вы ошибаетесь в первом бит: оставьте слово «список», и на самом деле очевидно, что в команде есть игроки. Тем не менее, вы ударили ноготь по голове вторым. Вы не хотите, чтобы клиенты вызывали
ateam.Players.Add(...)
. Вы действительно хотите, чтобы они набралиateam.AddPlayer(...)
. И ваша реализация (возможно, между прочим) вызоветPlayers.Add(...)
внутренне.
Надеюсь, вы увидите, насколько важна инкапсуляция для цели расширения ваших объектов. Вы хотите, чтобы каждый класс хорошо выполнял свою работу, не опасаясь вмешательства с других объектов.
Позволяет ли людям говорить
myTeam.subList(3, 5);
вообще иметь смысл? Если нет, то это не должно быть списком.
subList
больше не вернет Team
.
– Cruncher
18 February 2014 в 17:52
Прежде всего, это связано с удобством использования. Если вы используете наследование, класс Team
будет выставлять поведение (методы), которые предназначены исключительно для манипулирования объектами. Например, методы AsReadOnly()
или CopyTo(obj)
не имеют смысла для объекта команды. Вместо метода AddRange(items)
вы, вероятно, захотите получить более описательный метод AddPlayers(players)
.
Если вы хотите использовать LINQ, реализация универсального интерфейса, такого как ICollection<T>
или IEnumerable<T>
, имеет смысл .
Как уже упоминалось, композиция - это правильный путь. Просто реализуйте список игроков как приватную переменную.
Какие методы и свойства вы раскрываете - это дизайнерское решение. Какой базовый класс вы наследуете, это деталь реализации. Я чувствую, что стоит сделать шаг назад к первому.
Объект - это совокупность данных и поведения.
Итак, ваши первые вопросы должны быть:
Имейте в виду, что наследование подразумевает отношение «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», если вы не вникаете в абстрактные, особенно с течением времени и точные данные и поведение объектов в коде изменения. Это не должно заставлять вас всегда исключать наследование из классов коллекции; но это может быть наводящим на размышления.
Думаю, я не согласен с вашим обобщением. Команда - это не просто коллекция игроков. У команды есть гораздо больше информации об этом - имя, эмблема, сборник управленческого / административного персонала, сбор коучинговой команды, а затем сбор игроков. Таким образом, ваш класс 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();
}
Если вашим пользователям класса нужны все методы и свойства ** Список, вы должны извлечь из него свой класс. Если они им не нужны, приложите список и создайте обертки для методов, которые действительно нужны пользователям класса.
Это строгое правило, если вы пишете публичный API или любой другой код, который будет использоваться многими людьми. Вы можете игнорировать это правило, если у вас есть крошечное приложение и не более 2 разработчиков. Это сэкономит вам время.
Для маленьких приложений вы также можете выбрать другой, менее строгий язык. Ruby, JavaScript - все, что позволяет вам писать меньше кода.
class FootballTeam : List<FootballPlayer>
{
public string TeamName;
public int RunningTotal;
}
Предыдущий код означает: кучка парней с улицы, играющих в футбол, и у них, похоже, есть имя. Что-то вроде:
[/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.
}
означает: это футбольная команда, в которой есть руководство, игроки, администраторы и т. д. Что-то вроде:
[/g1]
Вот как ваша логика представлена на фотографиях ...
Просто потому, что я думаю, что другие ответы в значительной степени касаются касательной того, является ли футбольная команда «is-a» List<FootballPlayer>
или «has-a» List<FootballPlayer>
, которая на самом деле не отвечает на этот вопрос, как написано .
ОП в основном просит уточнить принципы наследования с List<T>
:
. В руководстве говорится, что вы не должны наследовать
blockquote>List<T>
. Почему нет?Поскольку
List<T>
не имеет виртуальных методов. Это меньше проблем в вашем собственном коде, поскольку вы обычно можете отключить реализацию с относительно небольшой болью, но может быть гораздо более крупной сделкой в публичном API.Что такое публикация API и почему мне это нужно?
blockquote>Открытый API - это интерфейс, который вы предоставляете сторонним программистам. Подумайте о каркасе. И помните, что ссылки, на которые ссылаются, - это «Рекомендации по дизайну .NET Framework », а не «Рекомендации по дизайну .NET ». Существует разница, и, вообще говоря, публичный дизайн API гораздо более строгий.
Если мой текущий проект не имеет и вряд ли когда-либо будет иметь этот открытый API, могу ли я безопасно игнорировать это руководство? Если я наследую List, и мне кажется, что мне нужен публичный API, какие трудности у меня есть?
blockquote>В значительной степени, да. Возможно, вам стоит подумать об обоснованности этого вопроса, чтобы убедиться, что оно применимо к вашей ситуации в любом случае, но если вы не создаете публичный API, вам не нужно беспокоиться об особенностях API, таких как управление версиями (из которых это подмножество).
Если вы добавите публичный API в будущем, вам нужно будет либо абстрагировать свой API от вашей реализации (не подвергая непосредственно ваше
List<T>
), либо нарушать рекомендации с возможным будущим боль, которая влечет за собой.Почему это даже имеет значение? Список - это список. Что может измениться? Что я могу изменить?
blockquote>Зависит от контекста, но поскольку мы используем
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 выполните то, что вы хотите.
Хотя у меня нет сложного сравнения, как большинство из этих ответов, я хотел бы поделиться своим методом для обработки этой ситуации. Расширяя 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
{
...
}
Это напоминает мне о «есть» и «имеет» компромисс. Иногда это проще и имеет смысл наследовать непосредственно из суперкласса. В других случаях имеет смысл создать отдельный класс и включить класс, который вы унаследовали бы от переменной-члена. Вы по-прежнему можете получить доступ к функциям класса, но не привязаны к интерфейсу или каким-либо другим ограничениям, которые могут возникнуть в результате наследования класса.
Что вы делаете? Как и во многих вещах ... это зависит от контекста. Руководство, которое я хотел бы использовать, состоит в том, что для наследования другого класса действительно должно быть отношение «есть». Так что, если вы пишете класс под названием BMW, он может унаследовать автомобиль, потому что BMW поистине автомобиль. Класс лошади может наследовать от класса млекопитающих, потому что лошадь на самом деле является млекопитающим в реальной жизни, и любая функциональность Млекопитающих должна относиться к Лошади. Но можете ли вы сказать, что команда - это список? Из того, что я могу сказать, это не похоже, что команда действительно «является» списком. Поэтому в этом случае у меня был бы List как переменная-член.
Мой грязный секрет: мне все равно, что говорят люди, и я это делаю. .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 не документировала причину, то это, безусловно, «лучшая практика», идущая из ниоткуда.
Collection<T>
не совпадает с List<T>
, поэтому для крупномасштабного проекта может потребоваться довольно много работы.
– Superbest
12 February 2014 в 16:31
Что говорится в рекомендациях, так это то, что публичный API не должен раскрывать внутреннее дизайнерское решение о том, используете ли вы список, набор, словарь, дерево или что-то еще. «Команда» не обязательно является списком. Вы можете реализовать его как список, но пользователи вашего общедоступного API должны использовать ваш класс в необходимости знать основы. Это позволяет вам изменить свое решение и использовать другую структуру данных, не затрагивая открытый интерфейс.
class FootballTeam : List<FootballPlayers>
, пользователи моего класса смогут скажите, что я унаследовал от List<T>
, используя рефлексию, увидев методы List<T>
, которые не имеют смысла для футбольной команды, и возможность использовать FootballTeam
в List<T>
, поэтому я бы был показывая детали реализации клиенту i> (без необходимости).
– 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
}
Что делать, если 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>
, если вы возвращаете его из метода.
В резюме
Задайте вопрос
X
a Y
? или Имеет X
a Y
? DefensivePlayers
, OffensivePlayers
, либо OtherPlayers
, может быть законно полезно иметь тип, который может использоваться кодом, который ожидает List<Player>
, но также включенным членов DefensivePlayers
, OffsensivePlayers
или SpecialPlayers
типа IList<DefensivePlayer>
, IList<OffensivePlayer>
и IList<Player>
. Можно использовать отдельный объект для кеширования отдельных списков, но инкапсулировать их в том же самом объекте, что и основной список, выглядят более чистыми [используйте недействительность перечислителя списка ...
– supercat
11 September 2014 в 21:48
Как все отметили, команда игроков не является списком игроков. Эта ошибка совершается многими людьми во всем мире, возможно, на разных уровнях знаний. Часто проблема тонкая, а иногда и очень грубая, как в этом случае. Такие проекты плохи, потому что они нарушают Принцип замещения Лискова. В Интернете есть много хороших статей, объясняющих эту концепцию, например http://en.wikipedia.org/wiki/Liskov_substitution_principle
Итак, есть два правила, которые должны быть сохранены в отношениях между родителями и детьми между классами:
Другими словами, родитель является необходимым определением для ребенка, а ребенок является достаточным определением родителя.
Вот способ продумать одно решение и применить вышеприведенный принцип, который должен помочь избежать такой ошибки. Следует проверить гипотезу, проверяя, действительны ли для всех классов родительский класс для производного класса как структурно, так и семантически.
Как вы видите, только первая характеристика списка применима к команде. Следовательно, команда не является списком. Список будет деталью реализации того, как вы управляете своей командой, поэтому ее следует использовать только для хранения объектов игрока и манипулировать с помощью методов класса Team.
На этом этапе я хотел бы отметить что класс Team должен, на мой взгляд, даже не реализовываться с использованием List; в большинстве случаев он должен быть реализован с использованием структуры данных Set (например, HashSet).
Когда они говорят, что List<T>
«оптимизирован», я думаю, они хотят иметь в виду, что у него нет таких функций, как виртуальные методы, которые стоят дороже. Таким образом, проблема заключается в том, что после того, как вы откроете List<T>
в своем общедоступном API , вы потеряете способность принудительно применять бизнес-правила или настраивать свои функции позже. Но если вы используете этот унаследованный класс как внутренний в своем проекте (в отличие от потенциально подверженного тысячам ваших клиентов / партнеров / других команд как API), тогда это может быть ОК, если это экономит ваше время, и это функциональность, которую вы хотите дублировать. Преимущество наследования с List<T>
заключается в том, что вы устраняете много немого кода обертки, который никогда не будет настроен в обозримом будущем. Кроме того, если вы хотите, чтобы ваш класс явно имел ту же семантику, что и List<T>
для жизни ваших API, также может быть и в порядке.
Я часто вижу, что многие люди делают тонны дополнительной работы только из-за Правило FxCop говорит так, или чей-то блог говорит, что это «плохая» практика. Много раз, это превращает код в дизайн палодовости паттерн. Как и в случае с множеством рекомендаций, относитесь к нему как к руководящему принципу, который может иметь исключения.
Наконец, некоторые предлагают обернуть список в чем-то:
Это правильный путь. «Безусловно многословие» - это плохой способ взглянуть на это. Он имеет явное значение, когда вы пишете
my_team.Players.Count
. Вы хотите подсчитать игроков.my_team.Count
.. ничего не значит. Считайте что?
Команда не является списком - состоят не только из списка игроков.
Если вы действительно обеспокоены тем, что он слишком многословный, вы всегда можете выставлять свойства из команда:
public int PlayerCount { get { return Players.Count; } }
.. которая становится:
my_team.PlayerCount
Теперь это имеет смысл и придерживается Закона Деметры .
Вы также должны рассмотреть возможность соблюдения принципа повторного использования Composite . Наследуя от
List<T>
, вы говорите, что команда - это список игроков и выставляя из нее ненужные методы. Это неверно. Как вы сказали, команда - это больше, чем список игроков: у нее есть имя, менеджеры, члены совета, тренеры, медицинский персонал, зарплата и т. Д. Если ваш класс команды содержит список игроков, вы «У команды есть список игроков», но у нее также могут быть и другие вещи.
my_team.Count
«ничего не значит». Для меня «подсчет команды» является совершенно смысловой концепцией. Я понимаю, почему вы можете настаивать на том, что на самом деле «учитываются игроки команды». - так это философское различие? Или есть практическая причина, почему упаковка лучше? Я жалуюсь на дополнительную работу, потому что каждый раз, когда я вернусь к этому коду, я подумаю: «Подождите, зачем я это обернул? почему бы просто не наследовать? & quot; и я не мог придумать комментарий, объясняющий это.
– Superbest
11 February 2014 в 05:11
List
, вы не можете наследовать что-то еще. Поэтому, если, например, позже вы хотите использовать ObservableCollection
(для привязки данных), вы введете нарушение.
– Aron
11 February 2014 в 07:24
. Когда вы рассматриваете свою команду как список игроков, вы проецируете «идею» команды шара ноги на один аспект: вы сокращаете «команду» до людей, которых вы видите на поле. Эта проекция верна только в определенном контексте. В другом контексте это может быть совершенно неправильно. Представьте, что вы хотите стать спонсором команды. Поэтому вам нужно поговорить с менеджерами команды. В этом контексте команда прогнозируется в списке своих менеджеров. И эти два списка обычно не перекрываются очень сильно. Другие контексты - текущие и прежние игроки и т. Д.
Таким образом, проблема с учетом команды в качестве списка ее игроков заключается в том, что ее семантика зависит от контекста и что он не может быть расширен при изменении контекста. Кроме того, трудно выразить, какой контекст вы используете.
Когда вы используете класс только с одним членом (например, IList activePlayers
), вы можете использовать имя участника (и, кроме того, его комментарий), чтобы сделать контекст понятным. Когда есть дополнительные контексты, вы просто добавляете дополнительный член.
В некоторых случаях может быть излишним создание дополнительного класса. Каждое определение класса должно быть загружено через загрузчик классов и будет кэшироваться виртуальной машиной. Это требует производительности и памяти во время выполнения. Когда у вас есть очень специфический контекст, может быть, стоит подумать о футбольной команде как о списке игроков. Но в этом случае вы действительно должны использовать только IList
, а не класс, полученный из него.
Когда у вас очень специфический контекст, все в порядке рассматривать команду как список игроков. Например, внутри метода вполне нормально писать
IList<Player> footballTeam = ...
При использовании F # может даже быть ОК, чтобы создать аббревиатуру типа
type FootballTeam = IList<Player>
. Но когда контекст более широкий или даже неясный, вы не должны этого делать. Это особенно важно, когда вы создаете новый класс, где неясно, в каком контексте он может использоваться в будущем. Предупреждающий знак - это когда вы начинаете добавлять дополнительные атрибуты в свой класс (имя команды, тренера и т. Д.). Это явный признак того, что контекст, в котором будет использоваться класс, не является фиксированным и изменится в будущем. В этом случае вы не можете рассматривать команду как список игроков, но вы должны моделировать список игроков (в настоящее время активных, а не травмированных и т. Д.) В качестве атрибута команды.
Это зависит от поведения вашего «командного» объекта. Если он ведет себя точно так же, как в коллекции, возможно, это будет нормально представлять его сначала с помощью простого списка. Тогда вы можете начать замечать, что вы продолжаете дублировать код, который повторяется в списке; на данный момент у вас есть возможность создать объект FootballTeam, который обертывает список игроков. Класс FootballTeam становится домом для всего кода, который повторяется в списке игроков.
Это делает мой код излишне подробным. Теперь я должен назвать my_team.Players.Count вместо my_team.Count. К счастью, с C # я могу определить индексаторов, чтобы сделать индексирование прозрачным, и переслать все методы внутреннего списка ... Но это много кода! Что я могу получить за все это?
blockquote>Инкапсуляция. Ваши клиенты не должны знать, что происходит внутри FootballTeam. Для всех ваших клиентов это может быть реализовано путем просмотра списка игроков в базе данных. Им не нужно знать, и это улучшает ваш дизайн.
Это просто не имеет никакого смысла. Футбольная команда не имеет «списка» игроков. Это список игроков. Вы не говорите: «Джон Макфуталлер присоединился к игрокам SomeTeam». Вы говорите: «Джон присоединился к SomeTeam». Вы не добавляете письмо в «символы строки», вы добавляете письмо к строке. Вы не добавляете книгу в книги библиотеки, вы добавляете книгу в библиотеку.
blockquote>Точно :) вы скажете footballTeam.Add (john), а не footballTeam.List.Add (Джон). Внутренний список не будет виден.
Позвольте мне переписать ваш вопрос. поэтому вы можете увидеть тему с другой точки зрения.
Когда мне нужно представлять футбольную команду, я понимаю, что это в основном имя. Например: «The Eagles»
string team = new string();
Затем я понял, что в командах также есть игроки.
Почему я не могу просто расширить строковый тип, чтобы он также содержал список игроков?
Ваша точка входа в проблему произвольна. Попытайтесь подумать, что делает команда (свойства), а не то, что она .
После этого вы можете увидеть, разделяет ли он свойства с другими классами. И подумайте о наследовании.
Классы должны избегать получения классов и вместо этого применять минимальные интерфейсы.
Вывод из классов прерывает инкапсуляцию :
. Среди прочего это затрудняет реорганизацию вашего кода.
Классы - это детали реализации, которые должны быть скрыты от других частей вашего кода. Короче говоря, 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 .
Это классический пример состава против наследования .
В этом конкретном случае:
Является ли команда списком игроков с добавленным поведением
или
Является ли команда собственностью, которая, как оказалось, содержит список игроков.
Расширяя список, вы ограничиваете себя несколькими способами:
Вы не можете ограничить доступ (например, остановить людей, изменяющих список). Вы получаете все методы List, хотите ли вы / хотите их всех или нет.
Что произойдет, если вы хотите иметь списки других вещей. Например, в командах есть тренеры, менеджеры, болельщики, оборудование и т. Д. Некоторые из них вполне могут быть списками в своем собственном праве.
Вы ограничиваете свои варианты наследования. Например, вы можете создать общий объект Team, а затем получить BaseballTeam, FootballTeam и т. Д. Чтобы наследовать из списка, вам нужно наследовать от Team, но это означает, что все команды разных типов вынуждены иметь одну и ту же реализацию этого списка.
Состав - включая объект, дающий поведение, которое вы хотите в своем объекте.
Наследование - ваш объект становится экземпляром объекта, у которого есть поведение, которое вы хотите.
Оба имеют свои применения, но это ясный случай, когда композиция предпочтительнее.
IEnumerable<T>
- a i>? – Eric Lippert 11 February 2014 в 20:15