风格上的差异:IDictionary与Dictionary


76

我有一个朋友,在使用Java开发了很长一段时间后才开始从事.NET开发,在看了他的一些代码后,我注意到他经常做以下事情:

IDictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();

他将字典声明为接口而不是类。通常,我会执行以下操作:

Dictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();

我只会在需要时使用IDictionary接口(例如,将字典传递给接受IDictionary接口的方法)。

我的问题是:他的做事方式有什么好处吗?这是Java的惯例吗?


11
对于局部变量,确实没有意义。
Joren


5
一个好的做法,但是要提防性能成本:nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue
2016年

1
IDictionaryDictionaryConcurrentDictionary)的两个主要实现具有非常不同的性能特征(例如Count,前者非常快而后者则非常慢)。因此,我的建议是IDictionary在可能的情况下在C#中指定具体的类(而不是),以便消费者知道如何以高效的方式使用字典。
mjwills

1
此外,IDictionary还有一些性能“怪癖” -nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue
mjwills

Answers:


67

如果IDictionary是比Dictionary更通用的类型,则在声明变量时使用更通用的类型是有意义的。这样,您不必太在乎分配给变量的实现类,并且将来可以轻松更改类型,而无需更改大量后续代码。例如,在Java中,通常认为这样做更好

List<Integer> intList=new LinkedList<Integer>();

比要做的事

LinkedList<Integer> intList=new LinkedList<Integer>();

这样,我确定所有下面的代码都将列表视为List而不是LinkedList,从而使将来轻松切换出Vector的LinkedList或实现List的任何其他类变得容易。我想说这对于Java和良好的编程来说是普遍的。


25
+1,但您可能想使用“通用”一词,而不是“通用”一词,因为该词已经附加了很多含义:)
AakashM,2009年

28

这种做法不仅限于Java。

当您想将对象的实例与所使用的类分离时,它也经常在.NET中使用。如果使用接口而不是类,则可以在需要时更改后备类型,而无需破坏其余代码。

您还将看到这种做法在处理IoC容器和使用Factory模式进行实例化时大量使用。


1
但是,您将失去Dictionary可能提供的IDictionary中没有提供的所有功能。
控制

10
但是,如果您能够使用IDictionary,那么...就不是在使用此功能。
贾斯汀·尼斯纳

1
但是,如果您只需要IDictionary中的功能...将IDictionary更改为Dictionary则比其他方法容易:)
Svish 2009年

1
将字典转换为IDictionary相对容易。:)
收服

10
失去Dictionary提供的功能但IDictionary不会提供的功能,是这个成语的重点。将本地变量声明为IDictionary会强制您将对该变量的使用限制为可通过接口使用的变量。然后,如果您以后发现必须使用实现IDictionay的其他类而不是Dictionary,则可以通过更改调用具体类构造函数的一行来实现(因为您知道您的代码仅将其用作IDictionary,而不是Dictionary )。
Stephen C. Steel


8

您应该始终尝试对接口进行编程,而不是对具体类进行编程。

用Java或任何其他面向对象的编程语言。

在.NET世界中,通常使用I来表示您正在使用的接口。我觉得这是比较常见的,因为在C#中,他们没有implementsextends参考类VS接口继承。

我想乳清会打字

 class MyClass:ISome,Other,IMore 
 { 
 }

你可以告诉ISome一个IMoreare接口Other是一个类

在Java中,不需要这样的东西

 class MyClass extends Other implements Some, More {
 }

该概念仍然适用,您应该尝试对接口进行编码。


3
我不认为他的问题是关于接口或接口如何工作或为什么以I开头。此外,必须在接口之前声明基类。所以,public class MyClass : BaseClass, IFirstInterface, ISecondInterface将是正确的。在界面的开头,我本来就没有什么特别的。编译器不会区别对待。这是其他程序员的标志。您可以在没有接口的情况下命名接口。。。但是其他程序员可能会讨厌您。
tandrewnichols 2012年

7

对于已经是实现细节的局部变量和私有字段,最好使用具体类型而不是接口进行声明,因为具体类可以提高性能(直接调度比虚拟/接口调度要快)。如果您不必在本地实现细节中强制转换为接口类型,则JIT还将能够更轻松地内联方法。如果从返回接口的方法返回了具体类型的实例,则转换是自动的。


4
那是一个微优化,仅当您有特定的性能瓶颈需要解决时才应执行,而不应该驱动设计。
斋,

8
这不是微优化。您已经在实现中,选择了具体的实现。为什么假装您没有以牺牲性能为代价?
山姆·哈威尔

5

据我所知,Java开发人员比.NET开发人员更倾向于使用抽象(和设计模式)。这似乎是它的另一个示例:为什么当他实际上只使用接口成员时,为什么要声明具体的类?


我一直陪着你,直到第二句话。您还应该如何获取对接口实现者的引用?您必须实例化实现它的东西。new IDictionary<string, double>()是行不通的,反正也没有任何意义。
汤姆W

我不太了解...我的意思是,曾经使用字典成员的人只想使用IDictionary功能。显然,您必须实例化某些内容,但是如果使用此模式,则以下代码对于使用该类的人员而言将具有相同的含义:IDictionary <T1,T2> dictionary = new Dictionary <T1,T2>(); 和IDictionary <T1,T2>字典= new ConcurrentDictionary <T1,T2>(); 如果您将不使用Dictionary或ConcreteDictionary特定成员,则不要将它们声明为类变量的类型,这会增加抽象层。
Gergely Orosz

4

大多数情况下,您会看到成员暴露于外部代码时使用的接口类型(IDictionary),无论该外部代码是在程序集外部还是在类外部。通常,大多数开发人员在使用接口类型公开封装的属性时,会在类定义内部使用具体类型。这样,他们可以利用具体类型的功能,但是如果他们更改了具体类型,则声明类的接口不需要更改。

公共类小部件
{
    私有Dictionary <string,string> map = new Dictionary <string,string>();
    公共IDictionary <字符串,字符串>映射
    {
        得到{返回地图;}
    }
}

以后可以变成:

class SpecialMap <TKey,TValue>:IDictionary <TKey,TValue> {...}

公共类小部件
{
    私有SpecialMap <string,string> map = new SpecialMap <string,string>();
    公共IDictionary <字符串,字符串>映射
    {
        得到{返回地图;}
    }
}

无需更改Widget的界面,也不必更改已经使用它的其他代码。


3

在上述情况下,几乎每个Java开发人员都将使用该接口来声明变量。Java集合的使用方式可能是最好的示例之一:

Map map = new HashMap();
List list = new ArrayList();

猜猜它在很多情况下只是实现松散耦合。


3

Java集合包括许多实现。因此,对我来说,使用它要容易得多

List<String> myList = new ArrayList<String>();

然后在将来,当我意识到我需要“ myList”具有线程安全性时,只需将这一行更改为

List<String> myList = new Vector<String>();

并且无需更改其他代码行。这也包括获取器/设置器。例如,如果您查看Map的实现数量,您可以想象为什么这可能是一种好的做法。在其他语言中,某件事只有几个实现(抱歉,.NET专家不多),但是在Objective-C中,实际上只有NSDictionary和NSMutableDictionary。因此,这没有什么意义。

编辑:

未能达到我的关键要点之一(与吸气剂/设定者暗指)。

以上内容使您可以:

public void setMyList(List<String> myList) {
    this.myList = myList;
}

使用此调用的客户端不必担心基础实现。使用符合他们可能拥有的List接口的任何对象。


将该行更改为:Vector <String> myList = new Vector <String>(); 也只换了一条线。
tster

1
@tster-但是还有哪些其他代码调用特定于ArrayList的方法,因为Vector没有完全相同的方法,因此必须更改这些方法吗?
Gordon Tucker)的

public void setMyList(List <String> list){myList = list; 在将其声明为List时有效,而在使用直接实现时则无效。也就是说,客户可以使用他们可能拥有的任何列表,而不必担心转换为您的实现。
MarkPowell

3

来自Java世界,我同意“深入接口程序”的口号已深入您的眼中。通过对接口(而不是实现)进行编程,可以使您的方法更易于扩展以适应未来的需求。


1
除非将来需要更改合同签名,例如新的属性或方法。
Matthew Whited

3

IDictionary是一个接口,Dictionary是一个类。

Dictionary实施IDictionary

这意味着可以Dictionary通过IDictionary实例引用实例,并通过实例调用大多数Dictionary方法和属性IDictionary

强烈建议使用尽可能多的接口,因为接口抽象了应用程序的模块和程序集,并允许多态性,这在许多情况和情况下非常普遍和有用,并且允许在不接触其他模块的情况下将一个模块替换为另一个模块。

假设目前,程序员写道:

IDictionary<string> dictionary = new Dictionary<string>();

现在dictionary调用的方法和属性Dictionary<string>

在未来的数据库尺寸已被长大了,我们发现,Dictionary<string>太慢了,所以我们要替换Dictionary<string>RedBlackTree<string>,这是更快。

因此,所有需要做的就是将上述说明替换为:

IDictionary<string> dictionary = new RedBlackTree<string>();

当然,如果RedBlackTree实现IDictionary了,则应用程序的所有代码都会成功编译,并且您拥有应用程序的新版本,该应用程序现在的性能比以前的版本更快,更高效。

没有接口,这种替换将更加困难,并且将要求程序员和开发人员更改更多可能导致错误的代码。


1

我发现对于局部变量,通常使用接口还是具体类都没关系。

与类成员或方法签名不同,如果您决定更改类型,则只需很少的重构工作,而变量在使用场所之外也不可见。实际上,当您使用var用来声明本地人时,您不是在获取接口类型而是在类类型(除非您显式转换为接口)。

但是,在声明方法,类成员或接口时,我认为预先使用接口类型而不是将公共API耦合到特定的类类型将为您省去很多麻烦。


2
这也是我的观点-对于方法定义,尝试和使用常规类型更为重要,因为该方法可以重用。对于局部变量,我没有看到优点。
控制

1
我个人认为优点是您将很容易发现实现类的可能依赖关系。例如,如果您将局部变量传递给实用程序方法之类的东西。
NickDK,2009年

2
对于局部变量,我所看到的参数是:1)使您可以严格使用接口,以及2)使代码对分配给本地的函数调用的返回类型不太敏感-这意味着该函数可以更改具体类型而不影响您的代码(当然,只要返回类型仍然坚持接口即可)。
LBushkin

1
@LBushkin,我还发现当您开发一种方法时,并且您进行诸如提取方法之类的重构时,即使应该使用该接口,具体类型也可能会传播。
Yishai

1

使用接口意味着以下代码中的“字典”可能是IDictionary的任何实现。

Dictionary1 dictionary = new Dictionary1();
dictionary.operation1(); // if operation1 is implemented only in Dictionary1() this will fail for every other implementation

隐藏对象的构造时最好看到:

IDictionary dictionary = DictionaryFactory.getDictionary(...);

我同意在使用工厂返回非特定字典时这是一个好主意。
控制

1
我的意思是,使用接口会迫使您以“通用”的术语进行思考,从而实现了类的解耦(在我的示例中,使用Dictionary1的方法必须熟悉类的内部结构,而不是接口)
Manrico Corazzi 09年

1

我在Java开发人员中也遇到过同样的情况。他以相同的方式实例化集合和对象到它们的接口。例如,

IAccount account = new Account();

属性总是被获取/设置为接口。这会导致序列化问题,在此进行了很好的解释


1
.NET在Web服务接口上使用的“魔术”序列化只是一个问题。使用接口而不是具体类的做法非常合理。
Daniel Pryden 09年

我们特别遇到了ASP.NET 1.1中的ViewState序列化问题
Jim Schubert,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.