Pregunta

Dado el siguiente árbol de herencia, ¿cuál sería la mejor manera de implementarlo de una manera que funcione?

abstract class Foo<T> : IEnumerable<T>
{
    public abstract Bar CreateBar();
}

class Bar<T> : Foo<T>
{
    // Bar's provide a proxy interface to Foo's and limit access nicely.
    // The general public shouldn't be making these though, they have access
    // via CreateBar()
    protected Bar(Foo base)
    {
        // snip...
    }
}

class Baz<T> : Foo<T>
{
    public Bar CreateBar()
    {
        return new Bar(this);
    }
}

Esto falla con: 'Bar.Bar ()' es inaccesible debido a su nivel de protección .

No quiero que el constructor sea público, solo las clases que heredan de Foo deberían poder crear Bar . Bar es un Foo especializado, y cualquier tipo de Foo debería poder crear uno. Público interno es una 'opción' aquí, ya que la mayoría de las extensiones predefinidas de Foo serán internas a la DLL, pero considero que esta es una respuesta descuidada, ya que cualquiera que venga más tarde y quiera crear una su propio tipo de Foo o Baz (que es probable que ocurra) se atascará con una implementación predeterminada de CreateBar () , que puede o puede No satisface sus necesidades.

¿Quizás hay una forma de refactorizar esto para que funcione bien? Me estoy golpeando la cabeza contra la pared tratando de diseñar esto para que funcione.

Editar (Más información):

Un poco más concreto: Foo está implementando IEnumerable y cuento largo, Bar está proporcionando la misma interfaz, pero a un subconjunto limitado de ese objeto enumerable. Todos los Foo deben poder crear subconjuntos de sí mismos (es decir, Bar) y devolverlos. Pero no quiero que todos los que quieran implementar un Foo tengan que preocuparse por esto, porque Bar hará el proxy y se preocupará por limitar el rango, etc.

¿Fue útil?

Solución

Bien, nueva respuesta:

  1. Dividir la barra en una interfaz y una clase concreta.
  2. Exprese el método de resumen público en términos de IBar.
  3. Haz de Bar una clase anidada privada en Foo, implementando IBar. Dale un constructor interno al que puedas llamar desde Foo.
  4. Escriba un método protegido en Foo que cree una instancia de Bar desde sí mismo. Las clases que se derivan de Foo pueden usar esto para implementar el método abstracto si solo el proxy es lo suficientemente bueno, y las clases con necesidades más complicadas pueden implementar IBar directamente. Incluso podría cambiar el método abstracto por uno virtual y crear una nueva barra a partir de " este " por defecto.

EDITAR: Una variante de esto sería hacer de Bar una clase anidada protegida dentro de Foo, con un constructor público. De esa manera, cualquier clase derivada sería capaz de crear una instancia por sí misma, pero ninguna clase no relacionada podría " ver " en absoluto. Aún necesitarías separar la interfaz de la implementación (para que la interfaz pueda ser pública) pero creo que eso es bueno de todos modos.

Otros consejos

¿Sería posible hacer que Baz sea un tipo anidado dentro de Bar? Esa es la única forma en que le dará más acceso a Bar de lo que tendría de otra manera. El solo hecho de tener la misma clase principal solo le da acceso a los miembros protegidos de Foo, y Foo no tiene acceso especial a Bar. Sospecho que hay otras formas tortuosas de hacer esto con tipos anidados, pero en realidad será bastante desagradable para los ingenieros de mantenimiento.

Sin embargo, es un diseño bastante extraño, forzar a una clase derivada a crear una instancia de una clase diferente derivada de la misma clase base. ¿Es eso realmente lo que necesitas? Quizás si lo explicas en términos más concretos, sería más fácil encontrar diseños alternativos.

Puedes acceder al constructor de Bar a través de un tipo anidado dentro de Foo:

abstract class Foo<T> : IEnumerable<T>
{
  public abstract Bar<T> CreateBar();

  protected Bar<T> CreateBar(Foo<T> f) { return new FooBar(f); }

  private class FooBar : Bar<T> 
   { public FooBar(Foo<T> f) : base(f) {}   
   }
}

class Bar<T> : Foo<T>
{ protected Bar(Foo<T> @base) {}
}

class Baz<T> : Foo<T>
{
    public override Bar<T> CreateBar() 
    {
        return CreateBar(this);
    }
}

Olvida, por un momento, que Bar deriva de Foo. Si haces esto, parece que el problema es " ¿Cómo lo hago para que cada subclase de Foo pueda crear una barra, incluso si la subclase no tiene acceso al constructor de Bar? & Quot;

Es un problema bastante fácil de resolver:

public class Foo
{
   protected static Bar CreateBarInstance()
   {
      return new Bar();
   }
   public virtual Bar CreateBar()
   {
      return CreateBarInstance();
   }
}

public class Bar
{
    internal Bar()
    {
    }
}

public class Baz : Foo
{
    public override Bar CreateBar()
    {
        Bar b = base.CreateBar();
        // manipulate the Bar in some fashion
        return b;
    }
}

Si desea garantizar que las subclases de Foo no tienen acceso al constructor de Bar, colóquelas en un ensamblaje diferente.

Ahora, para derivar Bar de Foo, es un cambio simple:

public class Bar : Foo
{
    internal Bar()
    {
    }
    public override Bar CreateBar()
    {
        throw new InvalidOperationException("I'm sorry, Dave, I can't do that.");
    }

}

" No quiero que el constructor sea público. " Compruebe.

" Solo las clases que heredan de Foo deberían poder crear barras. " Compruebe.

" Cualquier tipo de Foo debería poder crear uno. " Comprobar,

" Cualquier persona que venga después que quiera crear su propio tipo de Foo o Baz (lo que probablemente suceda) se quedará con una implementación predeterminada de CreateBar (), que puede o no satisfacer sus necesidades. " Esto depende bastante de lo que tenga que pasar con el método Foo.CreateBar (), creo.

C # no proporciona un equivalente directo de la palabra clave de amigo de C ++. Parece que tu diseño requiere este tipo de construcción.

En C ++, podría designar que una clase específica tiene acceso a los miembros privados / protegidos de otra clase utilizando " amigo " ;. Nota: este no es el mismo modificador interno de C # que da acceso a todas las clases dentro del mismo ensamblaje.

Mirando tu diseño, parece que estás tratando de hacer algo en la línea que requeriría un amigo de estilo C ++. Jon Skeet tiene razón, el diseño habitual en C # para compensar esto es usar una clase anidada.

Esta publicación en el foro explica más detalladamente y muestra algunos ejemplos de cómo hacerlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top