A difference in style: IDictionary vs Dictionary
我有一个朋友,他刚刚在Java开发了很多年后才进入.NET开发,在看过他的一些代码后,我注意到他经常这样做:
1 |
他将字典声明为接口而不是类。通常我会做以下工作:
1 |
我只在需要时使用IDictionary接口(例如,将字典传递给接受IDictionary接口的方法)。
我的问题是:他的做事方式有什么优点吗?在Java中这是常见的做法吗?
如果IDictionary是比Dictionary更通用的类型,那么在声明变量时使用更通用的类型是有意义的。这样,您就不必太在意分配给变量的实现类,将来您可以轻松地更改类型,而无需更改大量后续代码。例如,在Java中,通常认为做得更好。
1 |
比现在要做的还要多
1 |
这样,我确信所有下面的代码都将列表视为一个列表而不是LinkedList,这使得将来很容易为Vector或任何其他实现列表的类切换LinkedList。我认为这对于Java和一般的编程来说是很常见的。
这种做法不仅仅局限于Java。
它通常也用于.NET中,当您想从正在使用的类中分离对象的实例时。如果使用接口而不是类,则可以在需要时更改支持类型,而不必破坏其余代码。
您还将看到在处理IOC容器和使用工厂模式的声明时大量使用了这种实践。
你的朋友遵循了非常有用的原则:
"从实现细节中抽象自己"
对于已经是实现细节的局部变量和私有字段,使用具体类型比使用接口来声明要好,因为具体类提供了性能提升(直接调度比虚拟/接口调度更快)。如果不在本地实现细节中不必要地强制转换为接口类型,那么JIT还可以更容易地内联方法。如果从返回接口的方法返回具体类型的实例,则强制转换是自动的。
您应该总是尝试编程到接口,而不是具体的类。
在Java或任何其他面向对象的编程语言。
在.NET世界中,通常使用
我想乳清可以打字
1 2 3 | class MyClass:ISome,Other,IMore { } |
你可以告诉
在Java中不需要这样的东西
1 2 | class MyClass extends Other implements Some, More { } |
这个概念仍然适用,您应该尝试对接口进行编码。
就我所见,Java开发人员往往比.NET开发人员更频繁地使用抽象(和设计模式)。这似乎是另一个例子:当实质上只使用接口成员时,为什么要声明具体的类?
通常,您会看到当成员暴露于外部代码时使用的接口类型(IDictionary),无论是在程序集外部还是在类外部。通常,大多数开发人员在类定义内部使用具体类型,而使用接口类型公开封装的属性。通过这种方式,它们可以利用具体类型的功能,但是如果它们更改了具体类型,那么声明类的接口就不需要更改。
1 2 3 4 5 6 7 8 | public class Widget { private Dictionary<string, string> map = new Dictionary<string, string>(); public IDictionary<string, string> Map { get { return map; } } } |
以后会变成:
1 2 3 4 5 6 7 8 9 10 | class SpecialMap<TKey, TValue> : IDictionary<TKey, TValue> { ... } public class Widget { private SpecialMap<string, string> map = new SpecialMap<string, string>(); public IDictionary<string, string> Map { get { return map; } } } |
不需要改变小部件的接口,也不需要改变已经在使用它的其他代码。
来自Java世界,我同意"编程到接口"的咒语被钻入你的体内。通过编程到一个接口,而不是一个实现,您可以使您的方法更加可扩展,以满足未来的需求。
Java集合包括大量的实现。因此,我更容易利用
1 |
然后在将来,当我意识到我需要"mylist"是线程安全的,只需将这一行更改为
1 |
不要更改其他代码行。这也包括getter/setter。例如,如果您查看map的实现数量,您可以想象为什么这可能是一个好的实践。在其他语言中,只有两种实现(对不起,不是一个大的.NET用户),但是在Objective-C中,实际上只有nsDictionary和nsMutableDictionary。所以,这没什么意义。
编辑:
未能达到我的一个关键点(只是用getter/setter暗示)。
以上允许您:
1 2 3 | public void setMyList(List<String> myList) { this.myList = myList; } |
使用此调用的客户机不必担心底层实现。使用符合列表接口的任何对象。
在所描述的情况下,几乎每个Java开发人员都会使用接口来声明变量。使用Java集合的方式可能是最好的例子之一:
我猜它只是在很多情况下实现了松耦合。
我遇到了与Java开发人员相同的情况。他以相同的方式将集合和对象实例化到它们的接口。例如,
1 |
属性总是作为接口获取/设置。这会导致序列化出现问题,这里很好地解释了这一点。
使用接口意味着下面代码中的"dictionary"可能是IDictionary的任何实现。
1 2 | Dictionary1 dictionary = new Dictionary1(); dictionary.operation1(); // if operation1 is implemented only in Dictionary1() this will fail for every other implementation |
当您隐藏对象的结构时,最好看到:
1 | IDictionary dictionary = DictionaryFactory.getDictionary(...); |
我发现对于局部变量来说,使用接口或具体的类一般都不重要。
与类成员或方法签名不同的是,如果决定更改类型,重构工作很少,而且变量在其使用站点之外也不可见。事实上,当您使用
但是,在声明方法、类成员或接口时,我认为预先使用接口类型,而不是将公共API耦合到特定的类类型,这会让您省去不少麻烦。