Ale*_*rck 80 .net c# abstraction interface
像这样的问题的答案:List <T>或IList <T>似乎总是同意返回一个接口比返回一个集合的具体实现更好.但我正在努力解决这个问题.实例化接口是不可能的,因此如果您的方法返回一个接口,它实际上仍然返回一个特定的实现.我通过编写两个小方法来尝试一下这个:
public static IList<int> ExposeArrayIList()
{
return new[] { 1, 2, 3 };
}
public static IList<int> ExposeListIList()
{
return new List<int> { 1, 2, 3 };
}
Run Code Online (Sandbox Code Playgroud)
并在我的测试程序中使用它们:
static void Main(string[] args)
{
IList<int> arrayIList = ExposeArrayIList();
IList<int> listIList = ExposeListIList();
//Will give a runtime error
arrayIList.Add(10);
//Runs perfectly
listIList.Add(10);
}
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,当我尝试添加新值时,我的编译器没有给我任何错误,但显然IList<T>当我尝试向其添加内容时,将我的数组公开为暴露的方法会产生运行时错误.因此,那些不知道我的方法中发生了什么,并且必须为其添加值的人,被迫首先将我复制IList到a List以便能够添加值而不会有错误.当然,他们可以做一个类型检查,看看他们是否正在处理a List或者a Array,但是如果他们不这样做,并且他们想要将项目添加到集合中,他们别无选择将其复制IList到a List,即使它已经是一个List.数组永远不会暴露为IList?
我的另一个问题是基于相关问题的接受答案(强调我的):
如果您通过其他人将使用的库公开您的类,您通常希望通过接口而不是具体实现来公开它.如果您决定稍后更改类的实现以使用其他具体类,这将有所帮助.在这种情况下,您的库的用户将不需要更新他们的代码,因为接口不会更改.
如果你只是在内部使用它,你可能不在乎,使用List可能没问题.
想象一下有人实际上使用了我IList<T>从他的ExposeListIlist()方法中获得的,就像添加/删除值一样.一切正常.但现在就像答案所暗示的那样,因为返回一个接口更灵活,我返回一个数组而不是一个List(在我这边没问题!),然后他们正在接受治疗......
TLDR:
1)暴露界面会导致不必要的演员表?这没关系吗?
2)有时,如果库的用户不使用强制转换,那么当您更改方法时,它们的代码可能会中断,即使该方法仍然完好无损.
我可能正在过度思考这个问题,但我并不认为返回接口比返回实现更受欢迎.
Den*_*nis 107
也许这不是直接回答你的问题,但在.NET 4.5+中,我更喜欢在设计公共或受保护的API时遵循这些规则:
IEnumerable<T>如果只有枚举可用,则返回;IReadOnlyCollection<T>如果枚举和项目数都可用,则返回;IReadOnlyList<T>,如果枚举,项目计数和索引访问可用;ICollection<T>如果枚举,项目计数和修改可用,则返回;IList<T>,如果枚举,项目计数,索引访问和修改可用.最后两个选项假设,该方法不能将数组作为IList<T>实现返回.
Ham*_*jam 31
不,因为消费者应该知道IList到底是什么:
IList是ICollection接口的后代,是所有非泛型列表的基本接口.IList实现分为三类:只读,固定大小和可变大小.无法修改只读IList.固定大小的IList不允许添加或删除元素,但它允许修改现有元素.可变大小的IList允许添加,删除和修改元素.
您可以检查IList.IsFixedSize并IList.IsReadOnly做你想做的事情是什么.
我认为IList是一个胖接口的例子,它应该被拆分成多个较小的接口,并且当你将一个数组作为一个数组返回时它也违反了Liskov替换原则IList.
如果您想决定返回界面,请阅读更多内容
UPDATE
挖掘更多,我发现,IList<T>没有实现IList和 IsReadOnly通过基座接口进行访问ICollection<T>,但没有IsFixedSize进行IList<T>.阅读更多有关通用IList <>不继承非通用IList的原因?
Cod*_*ter 12
与所有"接口与实现"问题一样,您必须意识到暴露公共成员意味着什么:它定义了此类的公共API.
如果您公开一个List<T>成员(字段,属性,方法,...),您告诉该成员的消费者:通过访问此方法获得的类型是一个List<T>或从中派生的东西.
现在,如果您公开接口,则使用具体类型隐藏类的"实现细节".当然,你不能实例化IList<T>,但可以使用Collection<T>,List<T>,衍生或其自己的类型实施IList<T>.
在实际的问题是:"为什么不Array执行IList<T>",或"为什么有IList<T>接口,因此许多成员".
它还取决于您希望该成员的消费者做什么.如果您实际上通过您的Expose...成员返回内部成员,您new List<T>(internalMember)仍然希望返回一个,否则消费者可以尝试将其强制转换为IList<T>内部成员并通过该成员修改内部成员.
如果您只是希望消费者迭代结果,则公开IEnumerable<T>或IReadOnlyCollection<T>代替.
小心使用脱离上下文的一揽子引用.
返回接口比返回具体实现更好
如果它在SOLID原则的上下文中使用,那么这个引用才有意义.有5个原则,但为了讨论的目的,我们只谈谈最后3个.
一个应该"取决于抽象.不要依赖于结核."
在我看来,这个原则是最难理解的.但是如果仔细看一下这个引用,它看起来很像你原来的引用.
取决于接口(抽象).不依赖具体实现(具体结果).
这仍然有点混乱,但如果我们开始将其他原则应用于一起,它就会开始变得更有意义.
"程序中的对象应该可以替换为其子类型的实例,而不会改变该程序的正确性."
正如你所指出的那样,即使他们都实现了,返回一个Array明显不同于返回a的行为.这肯定违反了LSP.List<T>IList<T>
要认识到的重要一点是接口是关于消费者的.如果您要返回一个接口,那么您已经创建了一个契约,该接口可以使用该接口上的任何方法或属性,而无需更改程序的行为.
"许多客户端特定的接口比一个通用接口更好."
如果您要返回接口,则应返回实现支持的最客户端特定接口.换句话说,如果您不希望客户端调用该Add方法,则不应返回带有Add方法的接口.
不幸的是,.NET框架中的接口(特别是早期版本)并不总是理想的客户端特定接口.虽然@Dennis在他的回答中指出,但.NET 4.5+中有更多选择.
返回接口不一定比返回集合的具体实现更好。您应该始终有充分的理由使用接口而不是具体类型。在您的示例中,这样做似乎毫无意义。
使用接口的正当理由可能是:
你不知道返回接口的方法的实现是什么样的,随着时间的推移可能会有很多。可能是其他人写的,来自其他公司。因此,您只想就基本要求达成一致,而让他们决定如何实现该功能。
您希望以类型安全的方式公开一些独立于类层次结构的通用功能。应该提供相同方法的不同基类型的对象将实现您的接口。
有人可能会争辩说,1 和 2 的原因基本相同。它们是最终导致相同需求的两种不同场景。
“这是合同”。如果合同是与您自己签订的,并且您的应用程序在功能和时间方面都已关闭,那么使用接口通常没有意义。
| 归档时间: |
|
| 查看次数: |
4601 次 |
| 最近记录: |