2009-10-20 24 views
54

Tengo un amigo que acaba de entrar en el desarrollo .NET después de haber desarrollado en Java para las edades y, después de mirar un poco de su código que se dio cuenta de que está haciendo lo siguiente con bastante frecuencia:A diferencia de estilo: IDictionary vs Diccionario

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

Declara el diccionario como la Interfaz en lugar de la Clase. Normalmente yo haría lo siguiente:

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

sólo haría uso de la interfaz IDictionary cuando es necesario (por ejemplo, por ejemplo para pasar el diccionario a un método que acepta una interfaz IDictionary).

Mi pregunta es: ¿hay algún mérito en su forma de hacer las cosas? ¿Es esta una práctica común en Java?

+9

Para variables locales, realmente no tiene sentido. – Joren

+0

Dupe: http://stackoverflow.com/questions/1484445/why-are-variables-declared-with-the-interface-name-in-java –

+4

Una buena práctica ** PERO ** tenga cuidado con el costo de rendimiento: http : //www.nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue/ – MaYaN

Respuesta

51

Si IDictionary es un tipo "más genérico" que el diccionario, tiene sentido utilizar el tipo más genérico para declarar variables. De esta forma, no tiene que preocuparse demasiado por la clase de implementación asignada a la variable y puede cambiar el tipo fácilmente en el futuro sin tener que cambiar una gran cantidad de código siguiente. Por ejemplo, en Java a menudo se considera mejor que hacer

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

de lo que es hacer

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

De esa manera estoy seguro que todos siguiente código trata la lista como una lista y no un LinkedList, lo que facilita en el futuro el cambio de LinkedList para Vector o cualquier otra clase que implemente List. Yo diría que esto es común a Java y una buena programación en general.

+19

+1, pero es posible que desee utilice la palabra 'general' en lugar de 'genérico', que ya tiene una carga de significado adjunta :) – AakashM

24

Esta práctica no se limita solo a Java.

También se usa a menudo en .NET cuando desea desvincular la instancia del objeto de la clase que está utilizando. Si usa la interfaz en lugar de la clase, puede cambiar el tipo de respaldo siempre que sea necesario sin romper el resto del código.

También verá esta práctica muy utilizada en el tratamiento de contenedores de IoC y la instanciación con el patrón de Fábrica.

+0

Pero pierde toda la funcionalidad que el diccionario puede proporcionar y que no está en IDictionary. – rein

+8

Pero si puedes usar IDictionary en su lugar ... no estás usando la funcionalidad para empezar. –

+1

Pero si solo necesita la funcionalidad en IDictionary ... Es más fácil cambiar IDictionary a Dictionary que al revés :) – Svish

4

Por lo que he visto, los desarrolladores de Java tienden a usar abstracción (y patrones de diseño) con más frecuencia que los desarrolladores de .NET. Esto parece ser otro ejemplo: ¿por qué declarar la clase concreta cuando esencialmente solo estará trabajando con los miembros de la interfaz?

+0

Estuve contigo hasta la segunda oración. ¿De qué otra forma se supone que debes adquirir una referencia a un implementador de interfaz? Tienes que instanciar algo que lo implemente. 'new IDictionary ()' no funcionaría, y no tiene sentido de todos modos. –

+0

Realmente no entiendo ... mi punto fue que quien use el miembro del diccionario solo querrá usar las capacidades de IDictionary. Obviamente tiene que instanciar algo, pero si usa este patrón, los siguientes códigos significarán lo mismo para quien use la clase: IDictionary dictionary = new Dictionary (); e IDictionary dictionary = new ConcurrentDictionary (); Si no va a utilizar miembros específicos de Dictionary o ConcreteDictionary, tiene sentido no declararlos como el tipo de la variable de clase, agregando una capa de abstracción. –

1

He encontrado que, para variables locales, generalmente no importa mucho si usa la interfaz o la clase concreta.

A diferencia de los miembros de la clase o las firmas de métodos, hay muy poco esfuerzo de refactorización si decide cambiar los tipos, y la variable no está visible fuera de su sitio de uso. De hecho, cuando usa var para declarar locales, no obtiene el tipo de interfaz, sino el tipo de clase (a menos que se transfiera explícitamente a la interfaz).

Sin embargo, al declarar métodos, miembros de clase o interfaces, creo que ahorrará un poco de dolor de cabeza utilizar el tipo de interfaz por adelantado, en lugar de acoplar la API pública a un tipo de clase específico.

+2

Esta también era mi opinión: para las definiciones de métodos, es más importante probar y usar el tipo general ya que el método puede reutilizarse. Para las variables locales, no veo el mérito. – rein

+1

Personalmente creo que el mérito es el hecho de que detectará posibles dependencias en las clases de implementación con mucha facilidad. Como por ejemplo, si pasa su variable local a un método de utilidad o algo así. – NickDK

+2

Para las variables locales, los argumentos que he visto son que 1) te mantiene disciplinado al usar interfaces, y 2) permiten que tu código sea menos sensible a los tipos de retorno de las llamadas a función que asignan a los locales, lo que significa que la función puede cambiar el tipo concreto sin afectar su código (siempre y cuando el tipo de retorno aún se adhiera a la interfaz, por supuesto). – LBushkin

1

El uso de interfaces significa que "diccionario" en el siguiente código podría ser cualquier implementación de IDictionary.

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

Se ve mejor cuando se oculta la construcción del objeto:

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

Estoy de acuerdo en que cuando se utiliza una fábrica para devolver un diccionario no específico, esta es una buena idea. – rein

+1

Lo que quise decir es que usar la interfaz te obliga a pensar en términos "genéricos", imponiendo el desacoplamiento de las clases (en mi ejemplo, el método que usa Dictionary1 debe estar familiarizado con la estructura interna de la clase, no con la interfaz) –

12

su amigo está siguiendo el principio de gran utilidad:

"Resumen a sí mismo de los detalles de implementación"

+5

Pero estamos tratando con detalles de implementación. No estamos creando una interfaz para una clase; simplemente estamos declarando una variable local. – rein

+7

No realmente, en mi humilde opinión. Si los miembros de la interfaz son suficientes para usted, realmente le recomendaría que se quedara con la interfaz. Haga su código tan general como sea posible. –

3

En la situación descrita, casi todos los desarrolladores de Java usarían la interfaz para declarar la variable. La forma en que se utilizan las colecciones de Java es probablemente uno de los mejores ejemplos:

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

supongo que simplemente lleva a cabo el acoplamiento débil en muchas situaciones.

1

Las colecciones Java incluyen una multitud de implementaciones. Por lo tanto, es mucho más fácil para mí hacer uso de

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

Luego, en el futuro, cuando me doy cuenta de que necesito "miLista" para ser seguro para subprocesos simplemente cambiar esta sola línea a

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

Y el cambio no hay otra linea de codigo Esto incluye getters/setters también. Si observa el número de implementaciones de Map, por ejemplo, puede imaginar por qué esta podría ser una buena práctica. En otros idiomas, donde solo hay un par de implementaciones para algo (lo siento, no es un chico grande de .NET) pero en Objective-C realmente solo hay NSDictionary y NSMutableDictionary. Entonces, no tiene tanto sentido.

Editar:

fallas al golpear en una de mis puntos clave (recién aludidas con los captadores/definidores).

Lo anterior le permite tener:

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

Y el cliente utilizando esta llamada no necesita preocuparse por la implementación subyacente. Usar cualquier objeto que se ajuste a la interfaz de la Lista que puedan tener.

+0

cambiando esa línea a: Vector myList = new Vector (); también está cambiando solo una línea. – tster

+0

@tster - pero, ¿qué otro código llama a los métodos que son específicos de ArrayList que deberían modificarse porque Vector no tiene exactamente el mismo método? –

+0

public void setMyList (Lista list) { myList = list; } Es válido, al declararlo como una lista, no es así cuando se utiliza una implementación directa. Es decir. un cliente puede usar cualquier lista que pueda tener sin preocuparse por la conversión a su implementación. – MarkPowell

1

He encontrado la misma situación con un desarrollador de Java. Él ejemplifica colecciones Y objetos a su interfaz de la misma manera. Por ejemplo,

IAccount account = new Account(); 

Las propiedades se consiguen siempre/set como interfaces. Esto causa problemas con la serialización, que se explica muy bien here

+1

Esto es solo un problema con la serialización "mágica" que .NET usa en las interfaces de servicios web. La práctica de usar interfaces en lugar de clases concretas es perfectamente sólida. –

+0

Hemos tenido el problema específicamente con la serialización de ViewState en ASP.NET 1.1 –

4

Más a menudo, verá el tipo de interfaz (IDictionary) utilizado cuando el miembro está expuesto a código externo, ya sea fuera del ensamblaje o fuera de la clase. Normalmente, la mayoría de los desarrolladores usan el tipo concreto internamente para una definición de clase mientras exponen una propiedad encapsulada utilizando el tipo de interfaz. De esta forma, pueden aprovechar las capacidades del tipo concreto, pero si cambian el tipo concreto, la interfaz de la clase declarante no necesita cambiar.

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

más adelante puede llegar a ser:

 

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

sin cambiar la interfaz del widget y tener que cambiar otro tipo de código que ya lo utilizan.

7

Siempre debe intentar programar en la interfaz en lugar de la clase concreta.

En Java o cualquier otro lenguaje de programación orientado a objetos.

En .NET world es común usar un I para indicar que es una interfaz lo que está usando. Creo que esto es más común porque en C# no tienen implements y extends para referir la herencia de la clase frente a la interfaz.

creo suero de leche tendría que escribir

class MyClass:ISome,Other,IMore 
{ 
} 

y se puede decir ISome un IMore son interfaces mientras Other es una clase

En Java no hay necesidad de tal cosa

class MyClass extends Other implements Some, More { 
} 

El concepto aún se aplica, debe intentar codificar a la interfaz.

+2

No creo que su pregunta fue sobre cómo funcionan las interfaces o por qué comienzan con I. Además, las clases base deben declararse antes que las interfaces. Por lo tanto, 'public class MyClass: BaseClass, IFirstInterface, ISecondInterface' sería correcto. No hay nada intrínsecamente especial en mí al comienzo de una interfaz. El compilador no lo trata de manera diferente. Es un marcador para otros programadores. Podría nombrar sus interfaces sin ellos. . . pero otros programadores probablemente te odiarían. – tandrewnichols

4

Para variables locales y campos privados, que ya son detalles de implementación, es mejor usar tipos concretos que interfaces para las declaraciones porque las clases concretas ofrecen un aumento de rendimiento (el despacho directo es más rápido que el despacho virtual/de interfaz). El JIT también podrá aplicar métodos en línea más fácilmente si no se transfiere innecesariamente a los tipos de interfaz en los detalles de implementación local. Si se devuelve una instancia de un tipo concreto de un método que devuelve una interfaz, el lanzamiento es automático.

+4

Esa es una micro-optimización que solo debe realizarse si tiene un cuello de botella de rendimiento específico que resolver, no debe impulsar el diseño. – Yishai

+4

No es una micro-optimización. Ya estás en la implementación donde elegiste una implementación concreta. ¿Por qué pretender que no lo has hecho a expensas del rendimiento? –

3

Viniendo de un mundo de Java, estoy de acuerdo en que el "programa a una interfaz" es mantra en usted. Al programar en una interfaz, no en una implementación, usted hace que sus métodos sean más extensibles a las necesidades futuras.

+1

a menos que la necesidad futura sea un cambio en la firma del contrato, como una nueva propiedad o método. –

Cuestiones relacionadas