【问题标题】:A difference in style: IDictionary vs Dictionary风格上的差异:IDictionary vs Dictionary
【发布时间】:2010-12-08 09:44:08
【问题描述】:

我有一个朋友在使用 Java 开发多年后才刚刚进入 .NET 开发,在查看了他的一些代码后,我注意到他经常执行以下操作:

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

他将字典声明为接口而不是类。通常我会做以下事情:

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

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

我的问题是:他的做事方式有什么优点吗?这是 Java 中的常见做法吗?

【问题讨论】:

标签: c# java .net interface


【解决方案1】:

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

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

比现在做的

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

这样,我确信以下所有代码都将列表视为 List 而不是 LinkedList,这样将来就可以轻松地将 LinkedList 切换为 Vector 或任何其他实现 List 的类。我想说这对于 Java 和一般的良好编程来说很常见。

【讨论】:

  • +1,但您可能想要使用“通用”这个词而不是“通用”这个词,它已经附加了很多含义:)
【解决方案2】:

这种做法不仅限于 Java。

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

您还会看到这种做法大量用于处理 IoC 容器和使用工厂模式的实例化。

【讨论】:

  • 但是您失去了 Dictionary 可能提供的 IDictionary 中没有的所有功能。
  • 但是如果你能够使用 IDictionary 代替......你并没有使用开始的功能。
  • 但是如果您只需要 IDictionary 中的功能...将 IDictionary 更改为 Dictionary 比其他方式更容易:)
  • 将 Dictionary 转换为 IDictionary 比反过来更容易。 :)
  • 失去 Dictionary 提供但 IDictionary 没有的功能是这个习语的重点。将局部变量声明为 IDictionary 会强制您将该变量的使用限制为可通过接口获得的变量。然后,如果您后来发现必须使用其他实现 IDictionay 而不是 Dictionary 的类,您可以通过更改调用具体类构造函数的一行来做到这一点(因为您知道您的代码仅将其用作 IDictionary,而不是 Dictionary )。
【解决方案3】:

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

【讨论】:

  • 我和你在一起直到第二句话。您还应该如何获得对接口实现者的引用?你必须实例化实现它的东西。 new IDictionary&lt;string, double&gt;() 不起作用,而且无论如何都没有意义。
  • 我不太明白......我的意思是,曾经使用过字典成员的人只会想使用 IDictionary 功能。显然你必须实例化一些东西,但是如果你使用这个模式,下面的代码对于曾经使用过这个类的人来说都是一样的: IDictionary dictionary = new Dictionary();和 IDictionary 字典 = new ConcurrentDictionary();如果您不使用 Dictionary 或 ConcreteDictionary 特定成员,则不要将它们声明为类变量的类型,从而添加抽象层。
【解决方案4】:

我发现对于局部变量,通常使用接口还是具体类并不重要。

与类成员或方法签名不同,如果您决定更改类型,则几乎不需要重构工作,而且变量在其使用站点之外也不可见。事实上,当你使用var 声明局部变量时,你得到的不是接口类型,而是类类型(除非你明确地转换为接口)。

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

【讨论】:

  • 这也是我的观点——对于方法定义,尝试使用通用类型更为重要,因为该方法可以重用。对于局部变量,我没有看到优点。
  • 我个人认为优点是您将很容易发现对实现类的可能依赖关系。例如,如果您将局部变量传递给实用程序方法或其他东西。
  • 对于局部变量,我看到的参数是 1) 它让您在使用接口时保持自律,以及 2) 它允许您的代码对分配的函数调用的返回类型不太敏感对本地人来说——这意味着函数可以在不影响代码的情况下更改具体类型(当然,只要返回类型仍然遵循接口)。
  • @LBushkin,我还发现,当您正在开发一种方法,并且您进行诸如提取方法之类的重构时,即使应该使用接口,具体类型也可能会传播。
【解决方案5】:

使用接口意味着以下代码中的“字典”可能是 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(...);

【讨论】:

  • 我同意,当使用工厂返回非特定字典时,这是个好主意。
  • 我的意思是,使用接口迫使你以“通用”术语思考,强制解耦类(在我的示例中,使用 Dictionary1 的方法必须熟悉类,不带接口)
【解决方案6】:

你的朋友正在遵循非常有用的原则:

“从实现细节中抽象出来”

【讨论】:

  • 但是我们正在处理实现细节。我们不是创建一个类的接口——我们只是声明一个局部变量。
  • 不是真的,恕我直言。如果界面成员对您来说足够了,我真的建议您坚持使用界面。使您的代码尽可能通用。
【解决方案7】:

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

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

猜猜它只是在很多情况下完成了松散耦合。

【讨论】:

    【解决方案8】:

    Java 集合包括多种实现。因此,我使用起来更容易

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

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

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

    并且不更改任何其他代码行。这也包括 getter/setter。例如,如果您查看 Map 的实现数量,您可以想象为什么这可能是一个好习惯。在其他语言中,某些东西只有几个实现(对不起,不是一个大的 .NET 家伙),但在 Objective-C 中,实际上只有 NSDictionary 和 NSMutableDictionary。所以,这没有多大意义。

    编辑:

    未能达到我的关键点之一(只是用 getter/setter 提到它)。

    以上可以让你拥有:

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

    使用此调用的客户端无需担心底层实现。使用符合他们可能拥有的 List 接口的任何对象。

    【讨论】:

    • 将该行更改为: Vector myList = new Vector();也只换了一行。
    • @tster - 但是还有哪些其他代码调用特定于 ArrayList 的方法,因为 Vector 没有完全相同的方法而必须更改?
    • public void setMyList(List list) { myList = list; } 是有效的,当将其声明为 List 时,在使用直接实现时则不然。 IE。客户可以使用他们可能拥有的任何列表,而不必担心转换为您的实现。
    【解决方案9】:

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

    IAccount account = new Account();
    

    属性始终作为接口获取/设置。这会导致序列化出现问题,这个解释得很好here

    【讨论】:

    • 这只是 .NET 在 Web 服务接口上使用的“神奇”序列化的问题。使用接口而不是具体类的做法非常合理。
    • 我们在 ASP.NET 1.1 中特别遇到了 ViewState 序列化的问题
    【解决方案10】:

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

    公共类小部件 { 私有字典 map = new Dictionary(); 公共 IDictionary 映射 { 获取{返回地图; } } }

    以后可以变成:

    类 SpecialMap : IDictionary { ... } 公共类小部件 { 私有 SpecialMap map = new SpecialMap(); 公共 IDictionary 映射 { 获取{返回地图; } } }

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

    【讨论】:

      【解决方案11】:

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

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

      在 .NET 世界中,通常使用I 来表示您正在使用的接口。我认为这更常见,因为在 C# 中它们没有 implementsextends 来引用类与接口继承。

      我想乳清会打字

       class MyClass:ISome,Other,IMore 
       { 
       }
      

      你可以告诉ISomeIMore是接口,而Other是一个类

      在Java中不需要这样的东西

       class MyClass extends Other implements Some, More {
       }
      

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

      【讨论】:

      • 我不认为他的问题是关于接口如何工作或为什么它们以 I 开头。此外,必须在接口之前声明基类。所以,public class MyClass : BaseClass, IFirstInterface, ISecondInterface 是正确的。在界面的开头,I 本身并没有什么特别之处。编译器并没有区别对待它。它是其他程序员的标志。您可以在没有它们的情况下命名您的接口。 . .但其他程序员可能会讨厌你。
      【解决方案12】:

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

      【讨论】:

      • 这是一个微优化,只有在你有特定的性能瓶颈要解决时才应该执行,它不应该驱动设计。
      • 不是微优化。您已经在选择具体实现的实现中。为什么要假装你没有以牺牲性能为代价?
      【解决方案13】:

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

      【讨论】:

      • 除非未来需要更改合同签名,例如新的属性或方法。
      【解决方案14】:

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

      Dictionary 实现 IDictionary

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

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

      假设在当下,程序员写道:

      IDictionary&lt;string&gt; dictionary = new Dictionary&lt;string&gt;();

      现在dictionary 调用Dictionary&lt;string&gt; 的方法和属性。

      以后数据库越来越大了,我们发现Dictionary&lt;string&gt;太慢了,所以我们想用更快的RedBlackTree&lt;string&gt;替换Dictionary&lt;string&gt;

      所以所有需要做的就是将上面的指令替换为:

      IDictionary&lt;string&gt; dictionary = new RedBlackTree&lt;string&gt;();

      当然,如果RedBlackTree 实现了IDictionary,那么应用程序的所有代码都会成功编译,并且您拥有一个更新版本的应用程序,该应用程序现在比以前的版本运行得更快、更高效。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-07-07
        • 2016-06-05
        • 1970-01-01
        • 1970-01-01
        • 2010-09-24
        • 1970-01-01
        • 2010-11-08
        相关资源
        最近更新 更多