simple polimorfismo multiple interfaces herencia heredar clase c# inheritance multiple-inheritance

polimorfismo - ¿Debería C#tener herencia múltiple?



interface c# (30)

¡Prefiere la agregación sobre la herencia!

class foo : bar, baz

a menudo se maneja mejor con

class foo : Ibarrable, Ibazzable { ... public Bar TheBar{ set } public Baz TheBaz{ set } public void BarFunction() { TheBar.doSomething(); } public Thing BazFunction( object param ) { return TheBaz.doSomethingComplex(param); } }

De esta forma, puede intercambiar diferentes implementaciones de IBarrable e IBazzable para crear múltiples versiones de la aplicación sin tener que escribir otra clase.

La inyección de dependencia puede ayudar con esto mucho.

He encontrado numerosos argumentos en contra de la inclusión de la herencia múltiple en C #, algunos de los cuales incluyen (argumentos filosóficos aparte):

  • La herencia múltiple es demasiado complicada y a menudo ambigua
  • No es necesario porque las interfaces proporcionan algo similar
  • La composición es un buen sustituto donde las interfaces son inapropiadas

Vengo de un fondo de C ++ y extraño el poder y la elegancia de la herencia múltiple. Aunque no es adecuado para todos los diseños de software, existen situaciones en las que es difícil negar su utilidad sobre interfaces, composición y técnicas OO similares.

¿La exclusión de la herencia múltiple dice que los desarrolladores no son lo suficientemente inteligentes como para usarlos sabiamente y son incapaces de abordar las complejidades cuando surgen?

Personalmente agradecería la introducción de la herencia múltiple en C # (quizás C ##).

Anexo : Me interesaría saber a partir de las respuestas quién proviene de un fondo único (o de fondo procesal) versus un fondo de herencia múltiple. A menudo he encontrado que los desarrolladores que no tienen experiencia con la herencia múltiple a menudo recurren al argumento de la herencia múltiple-es-innecesario simplemente porque no tienen ninguna experiencia con el paradigma.


¡SÍ! ¡SÍ! ¡y si!

En serio, he estado desarrollando bibliotecas GUI en toda mi carrera, y MI (Herencia Múltiple) hace que esto sea más fácil que SI (Herencia única)

Primero hice SmartWin ++ en C ++ (MI muy usado) luego hice Gaia Ajax y finalmente ahora Ra-Ajax y yo podemos decir con mucha confianza que MI gobierna en algunos lugares. Uno de esos lugares son las bibliotecas de GUI ...

Y los argumentos que afirman que MI "es demasiado complejo" y así son puestos en su mayoría por personas que intentan construir guerras lingüísticas y que pertenecen al campamento que "actualmente no tiene IM" ...

Al igual que los lenguajes de programación funcionales (como Lisp) han sido enseñados (por los "no Lispers") como "demasiado complejos" por defensores del lenguaje de programación no funcional ...

La gente tiene miedo de lo desconocido ...

¡MI REGLAS!


Aquí hay un caso muy útil para la herencia múltiple que encuentro todo el tiempo.

Como proveedor de herramientas, no puedo cambiar las API publicadas o voy a romper la compatibilidad con versiones anteriores. Una cosa que resulta de eso es que nunca puedo agregar a una interfaz una vez que la he lanzado porque rompería la compilación para cualquiera que la implemente, la única opción es extender la interfaz.

Esto está bien para los clientes existentes, pero los nuevos verían que esta jerarquía es innecesariamente compleja, y si estuviera diseñando desde el principio, no optaría por implementarla de esta manera; tengo que hacerlo, o perderé la compatibilidad con versiones anteriores. . Si la interfaz es interna, entonces simplemente le agrego y arreglo los implementadores.

En muchos casos, el nuevo método para la interfaz tiene una implementación predeterminada obvia y pequeña, pero no puedo proporcionarlo.

Preferiría usar clases abstractas y luego, cuando tenga que agregar un método, agregar uno virtual con una implementación predeterminada, y algunas veces lo hacemos.

El problema, por supuesto, es si esta clase probablemente se mezclaría con algo que ya se está extendiendo, entonces no tenemos más remedio que usar una interfaz y tratar con las interfaces de extensión.

Si pensamos que tenemos este problema a lo grande, optamos por un modelo de evento rico en su lugar, que creo que es probablemente la respuesta correcta en C #, pero no todos los problemas se resuelven de esta manera, a veces se desea una interfaz pública simple. y uno más rico para los extenders.


C # admite herencia individual, interfaces y métodos de extensión. Entre ellos, proporcionan casi todo lo que ofrece la herencia múltiple, sin los dolores de cabeza que conlleva la herencia múltiple.


Creo que complicaría demasiado las cosas sin proporcionar suficiente ROI. Ya vemos gente carnicero con código .NET con árboles de herencia demasiado profundos. Puedo imaginar las atrocidades si la gente tiene el poder de hacer herencia múltiple.

No negaré que tiene potencial, pero simplemente no veo suficiente beneficio.


Creo que es simple en realidad. Al igual que cualquier otro paradigma de programación compleja, puede usarlo mal y lastimarse. Puedes mal usar objetos (¡oh sí!), Pero eso no significa que OO sea malo en sí mismo.

Del mismo modo con MI. Si no tiene un gran ''árbol'' de clases heredadas, o muchas clases que ofrecen los mismos métodos nombrados, entonces estará perfectamente bien con MI. De hecho, como MI proporciona la implementación, a menudo estará mejor que una implementación de SI donde tiene que volver a codificar o cortar y pegar métodos a objetos delegados. Menos código es mejor en estos casos ... puede crear un lío todopoderoso al compartir código al intentar reutilizar objetos a través de la herencia de la interfaz. Y esas soluciones no huelen bien.

Creo que el modelo de herencia única de .NET es defectuoso: deberían haber ido solo con interfaces, o solo MI. Tener "media y media" (es decir, herencia de implementación única más herencia de interfaz múltiple) es más confuso de lo que debería ser, y no elegante en absoluto.

Vengo de un fondo de MI, y no me asusta ni me quema.


De hecho, echo de menos la herencia múltiple por una razón específica ... el patrón de eliminación.

CADA vez que necesito implementar el patrón de eliminación, me digo a mí mismo: "Me gustaría poder derivar de una clase que implementa el patrón de disposición con algunas anulaciones virtuales". Copio y pego el mismo código de placa de caldera en cada clase que implementa IDispose y lo odio.


El CLR no admite la herencia múltiple de ninguna manera que yo sepa, por lo que dudo que pueda ser soportado de manera eficiente como en C ++ (o Eiffel, que puede hacerlo mejor dado que el lenguaje está específicamente diseñado). para mi).

Una buena alternativa a la Herencia Múltiple se llama Rasgos. Le permite mezclar varias unidades de comportamiento en una sola clase. Un compilador puede admitir rasgos como una extensión en tiempo de compilación para el sistema de tipo de herencia única. Simplemente declara que la clase X incluye los rasgos A, B y C, y el compilador junta los rasgos que solicita para formar la implementación de X.

Por ejemplo, supongamos que está intentando implementar IList (de T). Si observa diferentes implementaciones de IList (de T), a menudo comparten parte del mismo código exacto. Éstos son los rasgos que entran. Simplemente declaras un rasgo con el código común y puedes usar ese código común en cualquier implementación de IList (de T), incluso si la implementación ya tiene alguna otra clase base. Así es como se verá la sintaxis:

/// This trait declares default methods of IList<T> public trait DefaultListMethods<T> : IList<T> { // Methods without bodies must be implemented by another // trait or by the class public void Insert(int index, T item); public void RemoveAt(int index); public T this[int index] { get; set; } public int Count { get; } public int IndexOf(T item) { EqualityComparer<T> comparer = EqualityComparer<T>.Default; for (int i = 0; i < Count; i++) if (comparer.Equals(this[i], item)) return i; return -1; } public void Add(T item) { Insert(Count, item); } public void Clear() { // Note: the class would be allowed to override the trait // with a better implementation, or select an // implementation from a different trait. for (int i = Count - 1; i >= 0; i--) RemoveAt(i); } public bool Contains(T item) { return IndexOf(item) != -1; } public void CopyTo(T[] array, int arrayIndex) { foreach (T item in this) array[arrayIndex++] = item; } public bool IsReadOnly { get { return false; } } public bool Remove(T item) { int i = IndexOf(item); if (i == -1) return false; RemoveAt(i); return true; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator<T> GetEnumerator() { for (int i = 0; i < Count; i++) yield return this[i]; } }

Y usas el rasgo así:

class MyList<T> : MyBaseClass, DefaultListMethods<T> { public void Insert(int index, T item) { ... } public void RemoveAt(int index) { ... } public T this[int index] { get { ... } set { ... } } public int Count { get { ... } } }

Por supuesto, estoy arañando la superficie aquí. Para una descripción más completa, vea el documento Rasgos: Unidades Componibles de Comportamiento (PDF).

El lenguaje Rust (de Mozilla) ha implementado Rasgos de una manera interesante: notaron que los rasgos son similares a las implementaciones de interfaz predeterminadas, por lo que unificaron las interfaces y los rasgos en una sola característica (que llaman rasgos). La principal diferencia entre los rasgos y las implementaciones de interfaz por defecto (que ahora tiene Java) es que los rasgos pueden contener métodos privados o protegidos, a diferencia de los métodos de interfaz tradicionales que deben ser públicos. Si los rasgos y las interfaces no están unificados en una sola característica, entonces otra diferencia es que puede tener una referencia a una interfaz, pero no puede tener una referencia a un rasgo; un rasgo no es en sí mismo un tipo.


En lugar de herencia múltiple, puede usar mixins que es una mejor solución.


Estoy feliz de que C # no tenga Herencia Múltiple, aunque a veces sea conveniente. Lo que me gustaría ver en su lugar es la capacidad de proporcionar una implementación predeterminada de un método de interfaz. Es decir:

interface I { void F(); void G(); } class DefaultI : I { void F() { ... } void G() { ... } } class C : I = DefaultI { public void F() { ... } // implements I.F }

En este caso, ((I)new C()).F() llamará a la implementación de C de IF() , mientras que ((I)new C()).G() llamará a la implementación de IG() de DefaultI IG() .

Hay una serie de problemas que los diseñadores de idiomas tendrían que resolver antes de poder agregarlos al lenguaje, pero ninguno es muy difícil, y el resultado cubriría muchas de las necesidades que hacen que la Herencia múltiple sea deseable.


He estado trabajando con C # desde que estuvo disponible por primera vez como versión alfa / beta y nunca he olvidado una herencia múltiple. MI es bueno para algunas cosas, pero casi siempre hay otras formas de lograr el mismo resultado (algunas de las cuales terminan siendo más simples o crean una implementación más fácil de entender).


He publicado esto aquí un par de veces, pero creo que es genial. Puedes aprender a fingir MI here . También creo que el artículo destaca por qué MI es un dolor tan grande, incluso si eso no fue intencionado.

No lo extraño o lo necesito, prefiero usar la composición de los objetos para lograr mis fines. Ese también es el objetivo del artículo.


La herencia múltiple en general puede ser útil y muchos lenguajes OO la implementan de una forma u otra (C ++, Eiffel, CLOS, Python ...). ¿Es esencial? No. ¿Es bueno tenerlo? Sí.


Nunca lo he echado de menos ni una vez. Sí, [MI] se complica, y sí, las interfaces hacen un trabajo similar de muchas maneras, pero ese no es el punto más importante: en sentido general, simplemente no se necesita la mayor parte del tiempo. Incluso la herencia individual se usa en exceso en muchos casos.


Prefiero C ++. He usado Java, C #, etc. A medida que mis programas se vuelven más sofisticados en un entorno de OO de este tipo, me encuentro perdiendo Herencia Múltiple. Esa es mi experiencia subjetiva

Puede hacer que el increíble código de espagueti ... puede hacer que el código sea increíblemente elegante.


Si bien hay casos en los que puede ser útil, he descubierto que la mayoría de las veces cuando creo que lo necesito, realmente no lo hago.


También he usado herencias múltiples en C ++, pero realmente debes saber lo que estás haciendo para no tener problemas, especialmente si tienes dos clases base que comparten un abuelo. Entonces puede entrar en problemas con la herencia virtual, teniendo que declarar a cada constructor que va a llamar a la cadena (lo que hace que la reutilización binaria sea mucho más difícil) ... puede ser un desastre.

Más importante aún, la forma en que la CLI está construida actualmente impide que MI se implemente fácilmente . Estoy seguro de que podrían hacerlo si quisieran, pero tengo otras cosas que preferiría ver en la CLI que herencia múltiple.

Las cosas que me gustaría ver incluyen algunas características de Spec# , como tipos de referencia que no admiten nulos. También me gustaría ver más seguridad de objetos al poder declarar parámetros como const y la capacidad de declarar una función const (lo que significa que estás garantizando que el estado y el estado interno de un objeto no se modificarán con el método y la el compilador te revisa dos veces).

Creo que entre la herencia única, la herencia de interfaz múltiple, los genéricos y los métodos de extensión, puedes hacer prácticamente todo lo que necesites. Si algo puede mejorar las cosas para alguien que desee MI, creo que se necesita algún tipo de construcción de lenguaje que permita una agregación y composición más fácil. De esta forma, puede tener una interfaz compartida, pero luego delegar su implementación en una instancia privada de la clase de la que normalmente heredaría. En este momento, eso requiere mucho código de la placa de la caldera. Tener una característica de lenguaje más automatizada para eso ayudaría significativamente.



Uno de los problemas al tratar con la herencia múltiple es la distinción entre la herencia de la interfaz y la herencia de la implementación.

C # ya tiene una implementación limpia de la herencia de la interfaz (incluida la elección de implementaciones implícitas o explícitas) mediante el uso de interfaces puras.

Si observa C ++, para cada clase que especifique después de los dos puntos en la declaración de class , el tipo de herencia que obtenga estará determinado por el modificador de acceso ( private , protected o public ). Con public herencia public , obtienes el completo desorden de la herencia múltiple: múltiples interfaces se mezclan con múltiples implementaciones. Con herencia private , solo tienes implementación. Un objeto de " class Foo : private Bar " nunca se puede pasar a una función que espera una Bar porque es como si la clase Foo realmente solo tuviese un campo de Bar privado y un patrón de delegación implementado automáticamente.

La herencia de implementación múltiple pura (que en realidad es solo delegación automática) no presenta ningún problema y sería increíble tenerla en C #.

En cuanto a la herencia de interfaz múltiple de las clases, hay muchos diseños diferentes posibles para implementar la característica. Cada idioma que tiene herencia múltiple tiene sus propias reglas en cuanto a lo que ocurre cuando se llama a un método con el mismo nombre en múltiples clases base. Algunos lenguajes, como Common Lisp (particularmente el sistema de objetos CLOS) y Python, tienen un protocolo de metaobjetos donde se puede especificar la prioridad de la clase base.

Aquí hay una posibilidad:

abstract class Gun { public void Shoot(object target) {} public void Shoot() {} public abstract void Reload(); public void Cock() { Console.Write("Gun cocked."); } } class Camera { public void Shoot(object subject) {} public virtual void Reload() {} public virtual void Focus() {} } //this is great for taking pictures of targets! class PhotoPistol : Gun, Camera { public override void Reload() { Console.Write("Gun reloaded."); } public override void Camera.Reload() { Console.Write("Camera reloaded."); } public override void Focus() {} } var pp = new PhotoPistol(); Gun gun = pp; Camera camera = pp; pp.Shoot(); //Gun.Shoot() pp.Reload(); //writes "Gun reloaded" camera.Reload(); //writes "Camera reloaded" pp.Cock(); //writes "Gun cocked." camera.Cock(); //error: Camera.Cock() not found ((PhotoPistol) camera).Cock(); //writes "Gun cocked." camera.Shoot(); //error: Camera.Shoot() not found ((PhotoPistol) camera).Shoot();//Gun.Shoot() pp.Shoot(target); //Gun.Shoot(target) camera.Shoot(target); //Camera.Shoot(target)

En este caso, solo la implementación de la primera clase enumerada se hereda implícitamente en el caso de un conflicto. La clase para otros tipos base debe especificarse explícitamente para obtener sus implementaciones. Para hacerlo más a prueba de idiotas, el compilador puede rechazar la herencia implícita en el caso de un conflicto (los métodos conflictivos siempre requerirían un elenco).

Además, puede implementar herencia múltiple en C # hoy con operadores de conversión implícitos:

public class PhotoPistol : Gun /* ,Camera */ { PhotoPistolCamera camera; public PhotoPistol() { camera = new PhotoPistolCamera(); } public void Focus() { camera.Focus(); } class PhotoPistolCamera : Camera { public override Focus() { } } public static Camera implicit operator(PhotoPistol p) { return p.camera; } }

Sin embargo, no es perfecto, ya que no es compatible con is y as operadores, y System.Type.IsSubClassOf() .


Yo argumentaría en contra de la herencia múltiple simplemente por la razón que dices. Los desarrolladores lo usarán mal. He visto suficientes problemas con cada clase que hereda de una clase de utilidad, solo para que pueda invocar una función de cada clase sin necesidad de escribir tanto, para saber que la herencia múltiple daría lugar a código incorrecto en muchas situaciones. Lo mismo podría decirse sobre GoTo, que es una de las razones por las que su uso está tan mal visto. Creo que la herencia múltiple tiene algunos buenos usos, al igual que GoTo, en un mundo ideal, donde los dos solo se usaban cuando era apropiado, no habría problemas. Sin embargo, el mundo no es ideal, por lo que debemos proteger a los programadores malos de ellos mismos.


una de las cosas realmente agradables y (en el momento) novedosas sobre DataFlex 4GL v3 + (lo sé, lo sé, Data what ?) fue su soporte para la herencia de mixin : los métodos de cualquier otra clase podrían reutilizarse en su clase; siempre y cuando su clase proporcione las propiedades que usaron estos métodos, funcionó bien, y no hubo que preocuparse por el "problema de los diamantes" u otros "problemas" de herencia múltiple.

Me gustaría ver algo como esto en C #, ya que simplificaría ciertos tipos de abstracción y construcción.


Actualizar
Desafío a todos los que me votan para mostrarme algún ejemplo de herencia múltiple que no puedo transferir fácilmente a un idioma con herencia única. A menos que alguien pueda mostrar una muestra de este tipo, afirmo que no existe. He transferido toneladas de código C ++ (MH) a Java (no-MH) y eso nunca fue un problema, sin importar cuánto MH utilizara el código C ++.

Nadie podría demostrar hasta ahora que la herencia múltiple tiene alguna ventaja sobre otras técnicas que mencionaste en tu publicación (usando interfaces y delegados puedo obtener exactamente el mismo resultado sin mucho código o sobrecarga), mientras que tiene un par de desventajas bien conocidas ( diamante problema siendo los más molestos).

En realidad, la herencia múltiple generalmente se abusa. Si utiliza el diseño de OO para modelar de algún modo el mundo real en clases, nunca llegará al punto en que la herencia múltiple tenga sentido. ¿Puedes proporcionar un ejemplo útil para la herencia múltiple? La mayoría de los ejemplos que he visto hasta ahora son en realidad "incorrectos". Hacen de algo una subclase, que de hecho es solo una propiedad extra y, por lo tanto, una interfaz.

Echa un vistazo a Sather . Es un lenguaje de programación, donde las interfaces tienen herencia múltiple, como por qué no (no puede crear un problema de diamante), sin embargo, las clases que no son interfaces no tienen herencia alguna. Solo pueden implementar interfaces y pueden "incluir" otros objetos, lo que hace que estos otros objetos sean una parte fija de ellos, pero eso no es lo mismo que herencia, es más bien una forma de delegación (las llamadas a métodos "heredadas" al incluir objetos son de hecho, solo se reenvía a instancias de estos objetos encapsulados en su objeto). Creo que este concepto es bastante interesante y muestra que puedes tener un lenguaje de OO completo y limpio sin ninguna herencia de implementación.


No.

(para votar)


Give C# implicits and you will not miss multiple inheritance, or any inheritance for that matter.


I believe languages like C# should give the programmer the option. Just because it maybe too complicated does not mean it will be too complicated. Programming languages should provide the developer with tools to build anything the programmer wants to.

You choose to use those API''s a developer already wrote, you dont have too.


I try not to use inheritance. The less I can everytime.


If we introduce Multiple Inheritance then we are again facing the old Diamond problem of C++...

However for those who think it''s unavoidable we can still introduce multiple inheritance effects by means of composition (Compose multiple objects in an object and expose public methods which delegate responsibilities to composed object and return)...

So why bother to have multiple inheritance and make your code vulnerable to unavoidable exceptions...


No I do not. I use all other OO features to develop what I want. I use Interface and object encapsulation and I am never limited on what I want to do.


No unless Diamond problem is solved. and you can use composition till this is not solved.


No, we came away from it. You do need it now.