Sup*_*est 1299 .net c# oop inheritance list
在规划我的程序时,我经常从一连串的想法开始:
足球队只是一个足球运动员名单.因此,我应该代表它:
Run Code Online (Sandbox Code Playgroud)var football_team = new List<FootballPlayer>();
此列表的顺序表示球员在名单中列出的顺序.
但我后来才意识到,除了仅仅是球员名单之外,球队还有其他属性,必须记录下来.例如,本赛季的总得分,当前预算,统一颜色,string
代表球队名称等等.
那么我认为:
好吧,足球队就像一个球员名单,但另外,它有一个名称(a
string
)和一个总分(aint
)..NET不提供用于存储足球队的类,所以我将创建自己的类.最相似和相关的现有结构是List<FootballPlayer>
,所以我将继承它:Run Code Online (Sandbox Code Playgroud)class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
但事实证明,指南说你不应该继承List<T>
.我在两个方面完全被这个指南搞糊涂了.
显然List
是以某种方式针对性能进行了优化.怎么会这样?如果我延长会给我带来什么性能问题List
?究竟会打破什么?
我看到的另一个原因List
是微软提供的,我无法控制它,所以我在以后暴露"公共API"后无法改变它.但我很难理解这一点.什么是公共API,我为什么要关心?如果我当前的项目没有并且不太可能拥有此公共API,我可以放心地忽略此指南吗?如果我继承List
并且事实证明我需要一个公共API,那么我将遇到什么困难?
为什么它甚至重要?列表是一个列表.什么可能改变?我可能想要改变什么?
最后,如果微软不想让我继承List
,他们为什么不上课sealed
呢?
显然,对于自定义集合,Microsoft提供了一个Collection
应该扩展的类而不是List
.但这个类是非常裸露,并没有多少有用的东西,比如AddRange
,例如.jvitor83的答案提供了该特定方法的性能原理,但是如何缓慢AddRange
而不是没有AddRange
?
继承Collection
是比继承更多的工作List
,我认为没有任何好处.当然微软不会告诉我无缘无故地做额外的工作,所以我不禁觉得我在某种程度上误解了某些东西,而继承Collection
实际上并不是解决我问题的正确方法.
我见过如实施的建议IList
.就是不行.这是几十行样板代码,没有任何帮助.
最后,一些人建议包装内容List
:
class FootballTeam
{
public List<FootballPlayer> Players;
}
Run Code Online (Sandbox Code Playgroud)
这有两个问题:
它使我的代码不必要地冗长.我现在必须打电话my_team.Players.Count
而不是公正my_team.Count
.值得庆幸的是,使用C#我可以定义索引器以使索引透明,并转发内部的所有方法List
......但这是很多代码!我能为所有这些工作获得什么?
它只是简单没有任何意义.足球队没有"拥有"球员名单.这是球员名单.你没有说"John McFootballer加入了SomeTeam的球员".你说"约翰加入了SomeTeam".您不要在"字符串的字符"中添加字母,而是在字符串中添加字母.您不会将书籍添加到图书馆的书籍中,而是将图书添加到图书馆.
我意识到"引擎盖下"发生的事情可以说是"将X添加到Y的内部列表中",但这似乎是一种非常反直觉的思考世界的方式.
什么是代表一个数据结构,其中,"逻辑"(即,"以人的心灵")仅仅是一个正确的C#这样list
的things
与一些花俏?
继承List<T>
总是不可接受的?什么时候可以接受?为什么/为什么不呢?程序员在决定是否继承时必须考虑什么List<T>
?
Eri*_*ert 1467
这里有一些很好的答案.我会向他们添加以下几点.
什么是表示数据结构的正确C#方式,"逻辑上"(也就是说,"对人类的思维")只是一个带有一些花里胡哨的东西的列表?
问任何十个熟悉足球存在的非计算机程序员填写空白:
A football team is a particular kind of _____
Run Code Online (Sandbox Code Playgroud)
没有任何人说"有一些花俏的足球运动员名单",还是他们都说"运动队"或"俱乐部"或"组织"?你认为橄榄球队是一种特殊的球员名单的想法就在于你的人类思想和人类的思想.
List<T>
是一种机制.Football team是一个业务对象 - 也就是代表程序业务领域中某个概念的对象.不要混合那些!足球队是一种团队; 它有一个名单,名单是一个球员名单.名单不是一种特殊的球员名单.名单是一个球员名单.因此,让一个名为属性Roster
这是一个List<Player>
.ReadOnlyList<Player>
除非你相信每个了解足球队的人都会从球员名单中删除球员,否则在你参加比赛的同时做到这一点.
继承
List<T>
总是不可接受的?
谁不能接受?我?没有.
什么时候可以接受?
当您构建扩展List<T>
机制的机制时.
程序员在决定是否继承时必须考虑什么
List<T>
?
我是在构建机制还是业务对象?
但那是很多代码!我能为所有这些工作获得什么?
你花了更多的时间来输入你的问题,它会让你为List<T>
五十次相关成员编写转发方法.你显然不害怕冗长,我们在这里讨论的代码非常少; 这是几分钟的工作.
我更多地考虑了一下,还有另一个原因就是不要将足球队的模型列为球员名单.事实上,将足球队建模为拥有球员名单可能是一个坏主意.团队作为/拥有一个玩家列表的问题在于,你所获得的是团队在某个时刻的快照.我不知道你们这个班级的商业案例是什么,但是如果我有一个代表足球队的课程,我想问一下诸如"有多少海鹰队球员在2003年至2013年期间因伤缺席比赛?"之类的问题.或者"之前为另一支球队效力的丹佛球员在场上的同比增幅最大?" 或者" 今年小猪队一路走了吗? "
也就是说,在我看来,足球队很好地模仿了历史事实的集合,例如当一名球员被招募,受伤,退役等时.显然,当前的球员名单是一个重要的事实,应该是前面和 -中心,但是对于需要更多历史视角的对象,您可能还有其他有趣的事情要做.
m-y*_*m-y 149
哇,你的帖子有很多问题和要点.从微软获得的大多数推理都是恰到好处的.让我们从一切开始List<T>
List<T>
是高度优化.它的主要用途是用作对象的私有成员.class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }
.现在它就像做一样容易var list = new MyList<int, string>();
.IList<T>
如果您需要任何消费者拥有索引列表,您仍然可以将列表公开.这让你以后改变一个类中的实现.Collection<T>
非常通用,因为它是一个通用的概念...这个名字说明了一切; 它只是一个集合.还有更精确的版本,如SortedCollection<T>
,ObservableCollection<T>
,ReadOnlyCollection<T>
,等每一个实现IList<T>
,但不是 List<T>
.Collection<T>
允许成员(即添加,删除等)被覆盖,因为它们是虚拟的.List<T>
才不是.如果我正在编写此代码,那么类可能看起来像这样:
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.
}
Run Code Online (Sandbox Code Playgroud)
Nea*_*hal 136
class FootballTeam : List<FootballPlayer>
{
public string TeamName;
public int RunningTotal;
}
Run Code Online (Sandbox Code Playgroud)
以前的代码意味着:一群来自街头踢足球的家伙,他们碰巧有一个名字.就像是:
无论如何,这段代码(来自我的回答)
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.
}
Run Code Online (Sandbox Code Playgroud)
意思是:这是一支拥有管理,球员,管理员等的足球队.如:
这就是你的逻辑在图片中呈现的方式......
Tim*_*m B 112
在这个特定情况下:
该团队是否具有添加行为的玩家列表
要么
团队是否属于自己的对象,恰好包含一系列玩家.
通过扩展List,您可以通过多种方式限制自己:
您无法限制访问权限(例如,阻止人们更改名单).无论是否需要/想要它们,您都可以获得所有List方法.
如果你想要列出其他东西,会发生什么.例如,团队有教练,经理,粉丝,设备等.其中一些可能就是他们自己的名单.
您限制了继承选项.例如,您可能想要创建一个通用的Team对象,然后使用继承自该对象的BaseballTeam,FootballTeam等.要从List继承,您需要从Team继承,但这意味着所有不同类型的团队都被迫拥有该名单的相同实现.
合成 - 包括在对象中提供所需行为的对象.
继承 - 您的对象将成为具有所需行为的对象的实例.
两者都有它们的用途,但这是一个明显的情况,其中组成是优选的.
Sat*_*ina 66
正如大家所指出的那样,一队球员并不是球员名单.这个错误是由世界各地的许多人做出的,也许是在各种专业水平上.通常问题是微妙的,偶尔也很严重,就像在这种情况下一样.这样的设计很糟糕,因为这些违反了 Liskov替代原则.互联网有很多很好的文章解释这个概念,例如,http://en.wikipedia.org/wiki/Liskov_substitution_principle
总之,在类之间的父/子关系中有两个规则要保留:
换句话说,父母是孩子的必要定义,孩子是父母的充分定义.
这是一种思考解决方案并应用上述原则的方法,应该有助于避免这样的错误.人们应该通过验证父类的所有操作在结构和语义上是否对派生类有效来测试一个假设.
如您所见,只有列表的第一个特征适用于团队.因此,团队不是一个列表.列表将是您如何管理团队的实现细节,因此它应该仅用于存储播放器对象并使用Team类的方法进行操作.
在这一点上,我想说一下,在我看来,Team类甚至不应该使用List来实现; 在大多数情况下,它应该使用Set数据结构(例如HashSet)来实现.
Sam*_*ach 48
如果FootballTeam
拥有预备队和主队,该怎么办?
class FootballTeam
{
List<FootballPlayer> Players { get; set; }
List<FootballPlayer> ReservePlayers { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
你会如何模仿?
class FootballTeam : List<FootballPlayer>
{
public string TeamName;
public int RunningTotal
}
Run Code Online (Sandbox Code Playgroud)
这种关系显然有一个而不是一个.
还是RetiredPlayers
?
class FootballTeam
{
List<FootballPlayer> Players { get; set; }
List<FootballPlayer> ReservePlayers { get; set; }
List<FootballPlayer> RetiredPlayers { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
根据经验,如果您想要从集合继承,请为该类命名SomethingCollection
.
你的SomethingCollection
语义有意义吗?只有在你的类型是一个集合时才这样做Something
.
在FootballTeam
它听起来不对的情况下.A Team
不止一个Collection
.Team
其他答案指出,A 可以有教练,训练师等.
FootballCollection
听起来像是足球的集合,也可能是足球用具的集合.TeamCollection
,一个团队的集合.
FootballPlayerCollection
听起来像是一个玩家的集合,List<FootballPlayer>
如果你真的想这样做的话,它将是继承的类的有效名称.
真的List<FootballPlayer>
是一个非常好的类型来处理.也许IList<FootballPlayer>
如果你从一个方法返回它.
综上所述
问你自己
是 X
一个Y
?还是有 X
一个Y
?
我的班级名字是什么意思吗?
El *_*rko 36
您公开的方法和属性是设计决策.您继承的基类是实现细节.我觉得值得向前者退一步.
对象是数据和行为的集合.
所以你的第一个问题应该是:
请记住,继承意味着"isa"(是一种)关系,而组合意味着"有一种"(hasa)关系.在您的视图中根据您的情况选择合适的一个,同时考虑到应用程序发展过程中可能出现的情况.
在考虑具体类型之前,请考虑在界面中进行思考,因为有些人发现以这种方式将大脑置于"设计模式"更容易.
这不是每个人在日常编码中有意识地在这个级别上做的事情.但如果你正在考虑这类话题,那么你就是在设计水域中跋涉.意识到它可以解放.
查看MSDN或Visual Studio上的List <T>和IList <T>.了解他们公开的方法和属性.这些方法看起来都像是某个人想要在你的视图中对FootballTeam做些什么吗?
footballTeam.Reverse()对你有意义吗?footballTeam.ConvertAll <TOutput>()看起来像你想要的东西?
这不是一个棘手的问题; 答案可能真的是"是".如果你实现/继承List <Player>或IList <Player>,你就会被它们困住; 如果那是你的模特的理想选择,那就去做吧.
如果您决定是,那么这是有道理的,并且您希望您的对象可以作为玩家的集合/列表(行为)来处理,因此您希望通过各种方式实现ICollection或IList.名义上:
class FootballTeam : ... ICollection<Player>
{
...
}
Run Code Online (Sandbox Code Playgroud)
如果您希望对象包含玩家(数据)的集合/列表,并且您希望集合或列表成为属性或成员,请务必执行此操作.名义上:
class FootballTeam ...
{
public ICollection<Player> Players { get { ... } }
}
Run Code Online (Sandbox Code Playgroud)
您可能会觉得您希望人们只能枚举一组玩家,而不是计算它们,添加它们或删除它们.IEnumerable <Player>是一个非常有效的选项.
您可能会觉得这些接口根本不适用于您的模型.这种可能性较小(IEnumerable <T>在许多情况下都很有用),但它仍然可行.
任何试图告诉你其中一个在每种情况下都是明确和明确错误的人都是错误的.任何试图告诉你它的人在每种情况下都是绝对正确的,这是错误的.
一旦确定了数据和行为,就可以做出有关实施的决定.这包括您通过继承或组合依赖的具体类.
这可能不是一个很大的步骤,人们经常将设计和实施混为一谈,因为很可能在一两秒内完成所有操作并开始输入.
一个人为的例子:正如其他人所提到的,团队并不总是"只是"一组玩家.你是否为球队保留了一系列比赛分数?在你的模特中,球队是否可以与俱乐部互换?如果是这样,如果你的团队是一个球员集合,也许它也是一组员工和/或一组分数.然后你最终得到:
class FootballTeam : ... ICollection<Player>,
ICollection<StaffMember>,
ICollection<Score>
{
....
}
Run Code Online (Sandbox Code Playgroud)
设计尽管如此,在C#中你现在无法通过继承List <T>来实现所有这些,因为C#"only"支持单继承.(如果你在C++中尝试过这个malarky,你可能会认为这是一件好事.)通过继承和一个通过组合实现一个集合可能会感觉很脏.除非你明确地实现ILIst <Player> .Count和IList <StaffMember> .Count等,否则Count等属性会让用户感到困惑,然后它们只是痛苦而不是混乱.你可以看到它的发展方向; 在思考这条大道的同时,直觉可能会告诉你朝这个方向前进是错误的(正确或错误的是,如果你以这种方式实施它,你的同事也可能!)
关于不从集合类继承的指南不是C#特定的,您可以在许多编程语言中找到它.它是智慧而不是法律.一个原因是,在实践中,组合被认为通常在可理解性,可实现性和可维护性方面胜过继承.现实世界/域对象更常见的是找到有用且一致的"hasa"关系,而不是有用且一致的"isa"关系,除非你深入到抽象中,尤其是随着时间的推移以及代码中对象的精确数据和行为变化.这不应该导致您总是排除从集合类继承; 但它可能是暗示性的.
Dmi*_* S. 32
首先,它与可用性有关.如果使用继承,则Team
该类将公开纯粹为对象操作而设计的行为(方法).例如,AsReadOnly()
或者CopyTo(obj)
方法对团队对象没有意义.AddRange(items)
您可能需要更具描述性的AddPlayers(players)
方法,而不是方法.
如果你想使用LINQ,实现一个通用的接口,如ICollection<T>
或IEnumerable<T>
更有意义.
如上所述,构图是正确的方法.只需将玩家列表实现为私有变量即可.
Mau*_*tro 28
足球队不是足球运动员的名单.足球队由一系列足球运动员组成!
这在逻辑上是错误的:
class FootballTeam : List<FootballPlayer>
{
public string TeamName;
public int RunningTotal
}
Run Code Online (Sandbox Code Playgroud)
这是正确的:
class FootballTeam
{
public List<FootballPlayer> players
public string TeamName;
public int RunningTotal
}
Run Code Online (Sandbox Code Playgroud)
use*_*503 27
让我改写你的问题.所以你可能会从不同的角度看待这个主题.
当我需要代表足球队时,我明白它基本上就是一个名字.喜欢:"老鹰队"
string team = new string();
Run Code Online (Sandbox Code Playgroud)
后来我意识到球队也有球员.
为什么我不能只扩展字符串类型以便它还包含一个播放器列表?
你进入问题的观点是任意的.试着想一下团队有什么(属性),而不是它是什么.
执行此操作后,您可以看到它是否与其他类共享属性.并考虑继承.
ste*_*hke 26
当你将你的球队视为一个球员名单时,你就会将足球队的"想法"投射到一个方面:你将"球队"减少到你在场上看到的人.此投影仅在某些上下文中是正确的.在不同的背景下,这可能是完全错误的.想象一下,你想成为团队的赞助商.所以你必须和团队的经理谈谈.在此背景下,团队将被列入其经理名单.而这两个列表通常不会重叠很多.其他情况是当前与前任球员等.
因此,将团队视为其参与者列表的问题在于其语义取决于上下文,并且在上下文更改时无法扩展.此外,很难表达您正在使用的上下文.
当您使用只有一个成员(例如IList activePlayers
)的类时,您可以使用成员的名称(以及其注释)来清除上下文.当有其他上下文时,您只需添加一个额外的成员.
在某些情况下,创建一个额外的类可能有点过分.每个类定义必须通过类加载器加载,并由虚拟机缓存.这会花费您的运行时性能和内存.当你有一个非常具体的背景时,可以将足球队视为球员名单.但在这种情况下,你应该只使用一个 IList
,而不是从它派生的类.
当你有一个非常具体的背景时,可以将一个团队视为一个球员列表.例如,在一个方法中,写入是完全可以的
IList<Player> footballTeam = ...
Run Code Online (Sandbox Code Playgroud)
使用F#时,甚至可以创建类型缩写
type FootballTeam = IList<Player>
Run Code Online (Sandbox Code Playgroud)
但是当背景更广泛甚至不清楚时,你不应该这样做.特别是在创建新类时,不清楚将来可能在哪个上下文中使用它.警告标志是指您开始向班级添加其他属性(团队名称,教练等).这是一个明确的信号,表明将使用该类的上下文不是固定的,并且将来会发生变化.在这种情况下,您不能将团队视为球员列表,但您应该将(当前活动的,未受伤的等)球员的列表建模为球队的属性.
Mar*_*ett 20
仅仅因为我认为其他答案几乎与橄榄球队是"a-a" List<FootballPlayer>
还是"has-a"相关List<FootballPlayer>
,这实际上并没有像写的那样回答这个问题.
OP主要要求澄清继承的准则List<T>
:
指南说你不应该继承
List<T>
.为什么不?
因为List<T>
没有虚拟方法.这在您自己的代码中不是一个问题,因为您通常可以在相对较小的痛苦中切换实现 - 但在公共API中可能是一个更大的交易.
什么是公共API,我为什么要关心?
公共API是您向第三方程序员公开的接口.想想框架代码.回想一下,引用的指南是".NET Framework设计指南"而不是".NET 应用程序设计指南".有一点不同,而且 - 一般来说 - 公共API设计要严格得多.
如果我当前的项目没有并且不太可能拥有此公共API,我可以放心地忽略此指南吗?如果我继承List,结果我需要一个公共API,我会遇到什么困难?
差不多,是的.您可能想要考虑它背后的基本原理,看看它是否适用于您的情况,但如果您没有构建公共API,那么您不必担心API问题,如版本控制(其中,这是一个子集).
如果您将来添加公共API,则需要从实现中抽象出您的API(不要List<T>
直接暴露您的API )或违反指南以及可能的未来痛苦.
为什么它甚至重要?列表是一个列表.什么可能改变?我可能想要改变什么?
取决于上下文,但是因为我们使用的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!");
}
}
}
Run Code Online (Sandbox Code Playgroud)
啊......但你不能,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 */
}
Run Code Online (Sandbox Code Playgroud)
但是,如果你公开地暴露给第三方,你只是做了一个重大改变,将导致编译和/或运行时错误.
TL; DR - 指南适用于公共API.对于私有API,请执行您想要的操作.
Cru*_*her 17
允许人们说
myTeam.subList(3, 5);
Run Code Online (Sandbox Code Playgroud)
什么都没有意义?如果没有那么它不应该是List.
xpm*_*teo 16
这取决于"团队"对象的行为.如果它的行为就像一个集合,那么首先用普通的List表示它可能没问题.然后你可能会开始注意到你一直在重复列表上的迭代代码; 此时,您可以选择创建一个包装玩家列表的FootballTeam对象.FootballTeam类成为迭代玩家列表的所有代码的主页.
它使我的代码不必要地冗长.我现在必须调用my_team.Players.Count而不仅仅是my_team.Count.值得庆幸的是,使用C#我可以定义索引器以使索引透明,并转发内部List的所有方法......但这是很多代码!我能为所有这些工作获得什么?
封装.您的客户无需知道FootballTeam内部的情况.对于所有客户都知道,可以通过查看数据库中的播放器列表来实现.他们不需要知道,这可以改善您的设计.
它只是简单没有任何意义.足球队没有"拥有"球员名单.这是球员名单.你没有说"John McFootballer加入了SomeTeam的球员".你说"约翰加入了SomeTeam".您不要在"字符串的字符"中添加字母,而是在字符串中添加字母.您不会将书籍添加到图书馆的书籍中,而是将图书添加到图书馆.
确实:)你会说footballTeam.Add(john),而不是footballTeam.List.Add(john).内部列表将不可见.
Dis*_*ned 15
这里有很多优秀的答案,但我想触及一些我没有看到过的东西:面向对象的设计就是赋予对象权力.
您希望将所有规则,其他工作和内部详细信息封装在适当的对象中.通过这种方式,与此对象交互的其他对象不必担心这一切.实际上,您希望更进一步,并积极防止其他对象绕过这些内部.
从继承时List
,所有其他对象都可以将您视为List.他们可以直接访问添加和删除玩家的方法.你会失去控制力; 例如:
假设你想通过知道退役,辞职或被解雇来区分玩家离开的时间.您可以实现一个RemovePlayer
采用适当的输入枚举的方法.但是,通过继承List
,您将无法阻止直接访问Remove
,RemoveAll
甚至Clear
.结果,你实际上剥夺了你的FootballTeam
课程权力.
关于封装的其他想法......您提出了以下问题:
它使我的代码不必要地冗长.我现在必须调用my_team.Players.Count而不仅仅是my_team.Count.
你是对的,所有客户使用你的团队都是不必要的冗长.然而,与你暴露List Players
于所有和各种各样的事实相比,这个问题是非常小的,所以他们可以在未经你同意的情况下与你的团队混在一起.
你继续说:
它只是简单没有任何意义.足球队没有"拥有"球员名单.这是球员名单.你没有说"John McFootballer加入了SomeTeam的球员".你说"约翰加入了SomeTeam".
你错了第一点:删掉'list'这个词,显然团队确实有玩家.
然而,你用第二个击中头部的钉子.你不希望客户打电话ateam.Players.Add(...)
.你确实希望他们打电话ateam.AddPlayer(...)
.而你的实施将(可能在其他方面)Players.Add(...)
内部调用.
希望你能看到封装对赋予对象权力的重要性.您希望允许每个班级完成其工作,而不必担心来自其他对象的干扰.
Art*_*ena 12
表示数据结构的正确 C#方式是什么......
记住,"所有模型都错了,但有些模型很有用." - George EP Box
没有"正确的方法",只有一个有用的方法.
选择对您和/或您的用户有用的一个.而已.经济发展,不要过度工程.编写的代码越少,调试所需的代码就越少.(阅读以下版本).
- 编辑
我最好的答案是......这取决于.从List继承会将此类的客户端暴露给可能不应公开的方法,主要是因为FootballTeam看起来像一个业务实体.
- 第2版
我真诚地不记得我所说的"不要过度工程"评论.虽然我认为KISS的心态是一个很好的指导,但我想强调的是,从List继承业务类会产生比解决更多的问题,因为抽象泄漏.
另一方面,我认为有限数量的案例只是简单地从List继承是有用的.正如我在上一版中所写,这取决于.每个案例的答案都受到知识,经验和个人偏好的严重影响.
感谢@kai帮助我更准确地思考答案.
Pau*_*thy 11
这让我想起了"是一个"与"有一个"的权衡.有时直接从超类继承更容易,也更有意义.其他时候创建一个独立类并包含您将作为成员变量继承的类更有意义.您仍然可以访问该类的功能,但不会绑定到接口或可能来自该类继承的任何其他约束.
你做了什么?与许多事情一样......这取决于具体情况.我将使用的指南是,为了从另一个类继承,真正应该是"是一种"关系.因此,如果你写一个名为宝马的课程,它可以继承汽车,因为宝马真的是一辆汽车.马类可以继承哺乳动物类,因为马实际上是现实生活中的哺乳动物,任何哺乳动物的功能都应该与马相关.但是你能说一个团队是一个名单吗?从我所知道的情况来看,这似乎并不像一个团队真的"是一个"列表.所以在这种情况下,我会将List作为成员变量.
我的肮脏秘密:我不在乎别人说什么,我也这么做..NET Framework使用"XxxxCollection"进行传播(UIElementCollection用于我的头顶示例).
那让我停下来的是什么:
team.Players.ByName("Nicolas")
Run Code Online (Sandbox Code Playgroud)
当我发现它比它好
team.ByName("Nicolas")
Run Code Online (Sandbox Code Playgroud)
此外,我的PlayerCollection可能被其他类使用,例如"Club",没有任何代码重复.
club.Players.ByName("Nicolas")
Run Code Online (Sandbox Code Playgroud)
昨天的最佳做法,可能不是明天的做法.大多数最佳实践背后都没有理由,大多数只是社区之间的广泛共识.而不是问社区是否会在你这样做时责怪你,问自己,什么更具可读性和可维护性?
team.Players.ByName("Nicolas")
Run Code Online (Sandbox Code Playgroud)
要么
team.ByName("Nicolas")
Run Code Online (Sandbox Code Playgroud)
真.你有任何疑问吗?现在,您可能需要使用其他技术限制来阻止您在实际用例中使用List <T>.但是不要添加不应存在的约束.如果微软没有记录原因,那么它无疑是一种"最佳实践".
指南所说的是公共API不应该公开内部设计决定,无论是使用列表,集合,字典,树还是其他什么."团队"不一定是列表.您可以将其实现为列表,但是您的公共API的用户应该根据需要知道您的类.这允许您更改决策并使用不同的数据结构,而不会影响公共接口.
当他们说List<T>
"优化"时,我认为他们想要意味着它没有像虚拟方法那样有点贵的功能.所以问题在于,一旦List<T>
在公共API中公开,就会失去执行业务规则或稍后自定义其功能的能力.但是如果你在项目中使用这个继承的类作为内部(相对于可能暴露给成千上万的客户/合作伙伴/其他团队作为API),那么如果它节省你的时间并且它是你想要的功能可能没问题.重复.继承的优点List<T>
是你消除了许多在可预见的未来永远不会被定制的愚蠢的包装代码.此外,如果您希望您的类明确具有与List<T>
API的生命周期完全相同的语义,那么也可以.
我经常看到很多人做了大量的额外工作,仅仅是因为FxCop规则这么说,或者有人的博客说这是一个"坏"的做法.很多时候,这会将代码转换为设计模式palooza怪异.与许多指南一样,将其视为可能有例外的指导原则.
public class DemoList : List<Demo>
{
// using XmlSerializer this properties won't be seralized
// There is no error, the data is simply not there.
string AnyPropertyInDerivedFromList { get; set; }
}
public class Demo
{
// this properties will be seralized
string AnyPropetyInDemo { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
进一步阅读:当类继承自 List<> 时,XmlSerializer 不会序列化其他属性
我个人不会从 List 继承,而是实现 IList。Visual Studio 将为您完成这项工作并创建一个完整的工作实施。看这里:如何获得 IList 的完整工作实现
小智 5
虽然我没有像大多数这些答案那样进行复杂的比较,但我想分享我处理这种情况的方法。通过扩展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
{
...
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
167212 次 |
最近记录: |