2010-09-02 24 views
34

Realmente no lo entiendo.¿Por qué mi clase pública no puede extender una clase interna?

Si la clase base es abstracta y solo pretende ser utilizada para proporcionar funcionalidades comunes a las subclases públicas definidas en el ensamblado, ¿por qué no debería declararse interna?

No deseo que la clase abstracta sea visible para codificar fuera del ensamblaje. No quiero que el código externo lo sepa.

Respuesta

32

Al heredar de una clase, expone la funcionalidad de la clase base a través de su hijo.

Dado que la clase secundaria tiene una mayor visibilidad que su elemento primario, expondría miembros que de lo contrario estarían protegidos.

No se puede violar el nivel de protección de la clase principal implementando un elemento secundario con una mayor visibilidad.

Si la clase base está pensada para ser utilizada por clases públicas para niños, también debe hacer que el padre sea público también.

La otra opción es mantener su "padre" interno, que sea no abstracta, y lo utilizan para componer sus clases hijas, y el uso de una interfaz a obligar a las clases para implementar la funcionalidad:

public interface ISomething 
{ 
    void HelloWorld(); 
} 

internal class OldParent : ISomething 
{ 
    public void HelloWorld(){ Console.WriteLine("Hello World!"); } 
} 

public class OldChild : ISomething 
{ 
    OldParent _oldParent = new OldParent(); 

    public void HelloWorld() { _oldParent.HelloWorld(); } 
} 
+3

Gracias. Pero, ¿por qué los miembros de una clase interna no deberían considerarse internos, excepto cuando se expongan a través de una subclase pública? Creo que eso tiene sentido. – David

+0

Gracias por su respuesta detallada y sugerencia para usar la composición. ¿Estoy en lo cierto al pensar que la respuesta a mi pregunta es más 'porque C# no se creó de esa manera' que 'porque sería una cosa estúpida de hacer'? – David

+0

Es una cuestión de convención. Si está buscando cambiar el alcance de la clase a través de la herencia, simplemente no puede alterar las reglas para hacer que algo sea más accesible. Interna específicamente debe usarse para hacer que algo sea accesible para todas las clases en un ensamblado. Cambiar el alcance a público viola esto, haciendo que una subclase esté disponible para otros ensamblajes. Un buen uso de una clase interna es proteger las preocupaciones del marco central, por ejemplo.Si insiste en que su clase interna debería estar accesible fuera del ensamblado, probablemente no debería usar el modificador interno. –

3

Creo que esto violaría el Liskov Substitution Principle.

En casos como este, he usado clases internas y prefiero la composición a la herencia. ¿Hay algo acerca de su diseño que prohíba contener toda esa funcionalidad en su clase interna, y luego tener sus clases públicas contienen una instancia de esta clase interna?

+0

¿Cómo viola el principio de sustitución? No veo a qué te refieres aquí. –

+0

@Eric Lo tenía al revés, supongo. Usted viola la regla si su clase derivada elimina la funcionalidad proporcionada por la base, por lo que no podrá sustituir su base con su clase derivada. Cuando leí la pregunta por primera vez, salté a conclusiones. Eso fue sólo un pensamiento en ese momento, la parte principal de mi respuesta sigue siendo IMO, simplemente use composición en lugar de herencia. Gracias por la corrección. – Dave

13

Creo que lo más cerca que puede hacer es evitar que otros conjuntos de la creación de la clase abstracta por lo que es de constructores interna, para citar MSDN:

Un constructor interno impide la clase abstracta de ser utilizado como la base clase de tipos que no están en el mismo ensamblaje que la clase abstracta.

A continuación, puede intentar añadir un EditorBrowsableAttribute a la clase para tratar de ocultarlo de Intellisense (aunque, he tenido resultados mixtos que lo utilizan para ser honesto) o poner la clase base en un espacio de nombres anidados, como MyLibrary.Internals para separarlo del resto de sus clases.

+0

Ideas interesantes, gracias. – David

+2

+1: respuesta muy pragmática. –

38

ACTUALIZACIÓN: Esta pregunta era the subject of my blog on November 13th of 2012. Véalo para más ideas sobre este tema. Gracias por la gran pregunta!


Tienes razón; no tiene por qué ser así. Otros lenguajes de OO permiten "herencia privada", por lo que el hecho de que D herede de B solo puede aprovecharse por código que tiene la capacidad de ver B.

Esta fue una decisión de diseño de los diseñadores originales de C#. Desafortunadamente, ahora mismo estoy lejos de mi escritorio, me tomaré un par de días libres durante el fin de semana largo, así que no tengo las notas de diseño de idiomas de 1999 frente a mí. Si lo pienso cuando regrese lo buscaré y veré si hay una justificación para esta decisión.

Mi opinión personal es que la herencia se debe usar para representar "es un tipo de" relación; es decir, la herencia debe representar la semántica del dominio que se está modelando en el idioma. Intento evitar situaciones en las que la herencia se usa como un mecanismo de intercambio de código . Como otros han mencionado, probablemente sea mejor preferir la composición a la herencia si lo que quiere representar es "esta clase comparte mecanismos de implementación con otras clases".

+1

"prefiera la encapsulación a la herencia": ¿puede mostrar un ejemplo de esto? No estoy seguro de entender exactamente lo que quiere decir aquí, o cómo usaría la encapsulación para proporcionar código de implementación compartido. –

+0

@Erik: Tenía la intención de escribir "preferir composición" y tecleé accidentalmente "preferir la encapsulación". ¡Un error bastante grande! –

+0

Veo su punto, ¡aunque la barbilla en la arquitectura es un lujo para las personas con un mejor manejo del lenguaje que yo! (Quiero decir esto de la mejor manera posible, si por algún error se lee como sarcástico o agresivo.) – David

4

Creo que estás mezclando preocupaciones aquí, y C# tiene la culpa, en realidad (y Java antes).

La herencia debe servir como un mecanismo de categorización, mientras que a menudo se usa para la reutilización de código.

Para la reutilización de código, siempre se ha sabido que la composición supera la herencia. El problema con C# es que nos da una manera tan fácil para heredar:

class MyClass : MyReusedClass { } 

Pero con el fin de componer, tenemos que hacerlo por nosotros mismos:

class MyClass { 
    MyReusedClass _reused; 
    // need to expose all the methods from MyReusedClass and delegate to _reused 
} 

Lo que falta es un constructo como a trait (pdf), que llevará la composición al mismo nivel de usabilidad que la herencia.

Hay investigaciones sobre traits in C# (pdf), y sería algo como esto:

class MyClass { 
    uses { MyTrait; } 
} 

Aunque me gustaría ver another model (la de Perl 6 funciones).

ACTUALIZACIÓN:

Como nota al margen, el idioma Oxygene tiene a feature que le permite delegar todos los miembros de una interfaz a una propiedad de miembro que implementa esa interfaz:

type 
    MyClass = class(IReusable) 
    private 
    property Reused : IReusable := new MyReusedClass(); readonly; 
     implements public IReusable; 
    end; 

Aquí, todo los miembros de la interfaz de IReusable estarán expuestos a través de MyClass y todos delegarán en la propiedad Reused. Sin embargo, hay algunos problems con este enfoque.

Otra actualización:

he comenzado a implementar este concepto composición automática en C#: echar un vistazo a NRoles.

+0

Entiendo completamente su punto, pero siento que su afirmación de que "la composición gana a la herencia" podría expandirse. ¿Puedes decir algo más para explicar por qué piensas eso? Gracias. – David

+0

@David: Dije que la composición gana herencia para la reutilización de código. No debería tener que adherirse a una jerarquía de herencia por la única razón de reutilizar el código. La jerarquía dice algo sobre quién eres, y esa es una relación muy fuerte (y muy _precious_ en un lenguaje de herencia única). Para la reutilización del código, realmente quieres decir algo sobre lo que haces, y la composición y la delegación te permiten hacer exactamente eso. Las interfaces también son buenas en esto, pero aún necesita implementar sus miembros o delegar a alguien que sí lo haga. –

+0

El concepto de "herencia" en realidad incluye dos conceptos ortogonales: reutilización de código con comportamientos predeterminados y sustituibilidad. La herencia es apropiada cuando se requieren ambos; la composición es mejor cuando solo se requiere la reutilización del código.Lo que sería realmente genial sería el soporte genérico para la composición de interfaces, por lo que si II es una interfaz, un objeto podría implementar II en términos de un campo de ese tipo. Sin una restricción genérica para las interfaces, esto obviamente no funcionará, pero sería muy útil si Microsoft pudiera agregarlo en algún momento. – supercat

Cuestiones relacionadas