风格上的差异: 字典与字典

我有个朋友。NET 开发之后,在 Java 中开发了很多年,在看了他的一些代码之后,我注意到他经常做以下事情:

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

他将 dictionary 声明为 Interface 而不是 Class:

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

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

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

75605 次浏览

If IDictionary is a "more generic" type than Dictionary then it makes sense to use the more generic type in declaring variables. That way you don't have to care as much about the implementing class assigned to the variable and you can change the type easily in the future without having to change a lot of following code. For example, in Java it's often considered better to do

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

than it is to do

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

That way I'm sure all following code treats the list as a List and not a LinkedList, making it easy in the future to switch out LinkedList for Vector or any other class which implements List. I'd say this is common to Java and good programming in general.

This practice isn't just limited to Java.

It's often used in .NET as well when you want to de-couple the instance of the object from the class you're using. If you use the Interface rather than the Class, you can change the backing type whenever needed without breaking the rest of your code.

You'll also see this practice used heavily with dealing with IoC containers and instanciation using the Factory pattern.

As far as I've seen Java developers tend to use abstraction (and design patterns) more often than .NET developers. This seems another example of it: why declare the concrete class when he'll essentially only be working with the interface members?

I've found that for local variables it generally doesn't much matter whether you use the interface or the concrete class.

Unlike class members or method signatures, there is very little refactoring effort if you decide to change types, nor is the variable visible outside its usage site. In fact, when you use var to declare locals, you are not getting the interface type but rather the class type (unless you explicitly cast to the interface).

However, when declaring methods, class members, or interfaces, I think that it will save you quite a bit of headache to use the interface type up front, rather than coupling the public API to a specific class type.

Using interfaces means that "dictionary" in the following code might be any implementation of IDictionary.

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

It's best seen when you hide the construction of the object:

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

Your friend is following the very useful principle:

"Abstract yourself from implementation details"

In the described situation almost every Java developer would use the interface to declare the variable. The way the Java collections are used is probably one of the best examples:

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

Guess it just accomplishes loose coupling in a lot of situations.

Java Collections include a multitude of implementations. Therefore, it's much easier for me to make use of

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

Then in the future when I realize I need "myList" to be thread safe to simply change this single line to

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

And change no other line of code. This includes getters/setters as well. If you look at the number of implementations of Map for example, you can imagine why this might be good practice. In other languages, where there is only a couple implementations for something (sorry, not a big .NET guy) but in Objective-C there is really only NSDictionary and NSMutableDictionary. So, it doesn't make as much sense.

Edit:

Failed to hit on one of my key points (just alluded to it with the getter/setters).

The above allows you to have:

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

And the client using this call need not worry about the underlying implementation. Using whatever object that conforms to the List interface that they may have.

I've encountered the same situation with a Java developer. He instantiates collections AND objects to their interface in the same way. For instance,

IAccount account = new Account();

Properties are always get/set as interfaces. This causes problems with serialization, which is explained very well here

Most often, you see the interface type (IDictionary) used when the member is exposed to external code, whether that be outside the assembly or just outside the class. Typically, most developers use the concrete type internally to a class definition while they expose an encapsulated property using the interface type. In this way, they can leverage the concrete type's capabilities, but if they change the concrete type, the declaring class's interface doesn't need to change.

public class Widget
{
private Dictionary<string, string> map = new Dictionary<string, string>();
public IDictionary<string, string> Map
{
get { return map; }
}
}

later can become:



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; }
}
}

without changing Widget's interface and having to change other code already using it.

You should always attempt to program to the interface rather than the concrete class.

In Java or any other object oriented programming language.

In .NET world is common to use an I to indicate that is an interface what your're using. I think this is more common because in C# they don't have implements and extends to refer class vs interface inheritance.

I think whey would type

 class MyClass:ISome,Other,IMore
{
}

And you can tell ISome an IMore are interfaces while Other is a class

In Java there is no need for such a thing

 class MyClass extends Other implements Some, More {
}

The concept still applies, you should try to code to the interface.

For local variables and private fields, which are already implementation details, it's better to use concrete types than interfaces for the declarations because the concrete classes offer a performance boost (direct dispatch is faster than virtual/interface dispatch). The JIT will also be able to more easily inline methods if you don't unnecessarily cast to interface types in the local implementation details. If an instance of a concrete type is returned from a method that returns an interface, the cast is automatic.

Coming from a Java world, I agree that the "program to an interface" mantra is drilled into you. By programming to an interface, not an implementation, you make your methods more extensible to future needs.

IDictionary is an interface and Dictionary is a class.

Dictionary implements IDictionary.

That means that this is possible to refer to Dictionary instance with/by IDictionary instance and invoke most of the Dictionary methods and properties through IDictionary instance.

This is very recommended to use interfaces as many as possible, because interfaces abstracts the modules and assemblies of the applications, allows polymorphism, which is both very common and useful in many situations and cases and allows replacing one module by another without touching the other modules.

Suppose that in the present, the programmer wrote:

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

And now dictionary invokes the methods and properties of Dictionary<string>.

In the future the databases has been grown up in size and we find out that Dictionary<string> is too slow, so we want to replace Dictionary<string> by RedBlackTree<string>, which is faster.

So all what is needed to be done is replacing the above instruction to:

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

Of course that if RedBlackTree implements IDictionary then all the code of the application compiles successfully and you have a newer version of your application, where the application now performs faster and more efficient than the previous version.

Without interfaces, this replacement would be more difficult to do and would require the programmers and developers to change more code that is potential to bugs.