Pregunta

¿Hay una manera de obligar a una clase (secundaria) a tener constructores con firmas particulares o métodos estáticos particulares en C # o Java?

Obviamente no puedes usar interfaces para esto, y sé que tendrá un uso limitado. Una instancia en la que lo encuentro útil es cuando desea aplicar alguna directriz de diseño, por ejemplo:

Excepciones
Todos deberían tener los cuatro constructores canónicos, pero no hay forma de imponerlos. Tienes que confiar en una herramienta como FxCop (caso C #) para capturar estos.

Operadores
No hay ningún contrato que especifique que se pueden sumar dos clases (con operador + en C #)

¿Hay algún patrón de diseño para evitar esta limitación? ¿Qué construcción podría agregarse al lenguaje para superar esta limitación en futuras versiones de C # o Java?

¿Fue útil?

Solución

No se aplica en tiempo de compilación, pero he pasado mucho tiempo analizando problemas similares; una biblioteca de matemáticas genérica habilitada , y una eficiente -default) la API de ctor está disponible en MiscUtil . Sin embargo, estos solo se verifican en el primer uso en tiempo de ejecución. En realidad, esto no es un gran problema: las pruebas de su unidad deben encontrar cualquier operador / ctor faltante muy rápidamente. Pero funciona, y muy rápido ...

Otros consejos

Al usar los genéricos, puedes forzar que un argumento de tipo tenga un constructor sin parámetros, pero ese es el límite.

Aparte de los genéricos, sería complicado usar estas restricciones incluso si existieran, pero a veces podría ser útil para los parámetros / argumentos de tipo. Permitir que los miembros estáticos en interfaces (o posiblemente interfaces estáticas) también podría ayudar con el " operador numérico genérico " problema.

I escribí sobre esto hace poco cuando enfrentaba un problema similar.

Puedes usar el patrón de Fábrica.

interface Fruit{}

interface FruitFactory<F extends Fruit>{
   F newFruit(String color,double weight);

   Cocktail mixFruits(F f1,F f2);
}

Podrías crear clases para cualquier tipo de fruta

class Apple implements Fruit{}
class AppleFactory implements FruitFactory<Apple>{
   public Apple newFruit(String color, double weight){
       // create an instance
   }
   public Cocktail mixFruits(Apple f1,Apple f2){
       // implementation
   }
}

Esto no exige que no pueda crear una instancia de otra manera que no sea utilizando Factory, pero al menos puede especificar qué métodos solicitaría de una Factory.

Constructores de fuerzas

No puedes. Lo más cerca que puede venir es hacer que el constructor predeterminado sea privado y luego proporcionar un constructor que tenga parámetros. Pero todavía tiene lagunas.

class Base
{
  private Base() { }
  public Base(int x) {}
}

class Derived : Base
{
  //public Derived() { } won't compile because Base() is private
  public Derived(int x) :base(x) {}
  public Derived() : base (0) {} // still works because you are giving a value to base
}

El problema en el lenguaje es que los métodos estáticos son en realidad ciudadanos de segunda clase (un constructor también es un tipo de método estático, porque no necesitas una instancia para comenzar).

Los métodos estáticos son solo métodos globales con un espacio de nombres, en realidad no " pertenecen " a la clase en la que están definidos (OK, tienen acceso a métodos privados (estáticos) en la clase, pero eso es todo).

El problema en el nivel del compilador es que sin una instancia de clase no tienes una tabla de funciones virtuales, lo que significa que no puedes usar toda la herencia y el polimorfismo.

Creo que uno podría hacer que funcione agregando una tabla virtual global / estática para cada clase, pero si aún no se ha hecho, probablemente haya una buena razón para ello.

Aquí está lo resolvería si fuera un diseñador de idiomas.

Permitir que las interfaces incluyan métodos estáticos, operadores y constructores.

interface IFoo  
{  
  IFoo(int gottaHaveThis);  
  static Bar();  
}

interface ISummable
{
      operator+(ISummable a, ISummable b);
}

No permitir el nuevo IFoo (someInt) correspondiente o IFoo.Bar()

Permitir que los constructores se hereden (al igual que los métodos estáticos).

class Foo: IFoo
{
  Foo(int gottaHaveThis) {};
  static Bar() {};
}

class SonOfFoo: Foo 
{
  // SonOfFoo(int gottaHaveThis): base(gottaHaveThis); is implicitly defined
}

class DaughterOfFoo: Foo
{
  DaughhterOfFoo (int gottaHaveThis) {};
}

Permita que el programador convierta a interfaces y verifique, si es necesario, en tiempo de ejecución si la conversión es semánticamente válida incluso si la clase no especifica explícitamente.

ISummable PassedFirstGrade = (ISummable) 10; 

Lamentablemente no puedes en C #. Aquí hay un golpe en eso aunque:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Foo.Instance.GetHelloWorld());
        Console.ReadLine();
    }
}

public class Foo : FooStaticContract<FooFactory>
{
    public Foo() // Non-static ctor.
    {
    }

    internal Foo(bool st) // Overloaded, parameter not used.
    {
    }

    public override string GetHelloWorld()
    {
        return "Hello World";
    }
}

public class FooFactory : IStaticContractFactory<Foo>
{
    #region StaticContractFactory<Foo> Members

    public Foo CreateInstance()
    {
        return new Foo(true); // Call static ctor.
    }

    #endregion
}

public interface IStaticContractFactory<T>
{
    T CreateInstance();
}

public abstract class StaticContract<T, Factory>
    where Factory : IStaticContractFactory<T>, new() 
    where T : class
{
    private static Factory _factory = new Factory();

    private static T _instance;
    /// <summary>
    /// Gets an instance of this class. 
    /// </summary>
    public static T Instance
    {
        get
        {
            // Scary.
            if (Interlocked.CompareExchange(ref _instance, null, null) == null)
            {
                T instance = _factory.CreateInstance();
                Interlocked.CompareExchange(ref _instance, instance, null);
            }
            return _instance;
        }
    }
}

public abstract class FooStaticContract<Factory>
    : StaticContract<Foo, Factory>
    where Factory : IStaticContractFactory<Foo>, new() 
{
    public abstract string GetHelloWorld();
}

Bueno, sé por la redacción de su pregunta que está buscando la aplicación en tiempo de compilación. A menos que alguien más tenga una brillante sugerencia / pirateo que le permita hacer esto de la forma en que está insinuando al compilador, le sugiero que escriba una tarea personalizada de MSbuild que hizo esto. Un marco de AOP como PostSharp puede ayudarlo a lograrlo en todo momento al realizar una copia del modelo de tareas de compilación.

¿Pero qué tiene de malo el análisis de código o la ejecución en tiempo de ejecución? Tal vez sea una preferencia y lo respeto, pero personalmente no tengo problemas con que CA / FXCop compruebe estas cosas ... y si realmente quiere forzar a los implementadores de sus clases a tener firmas de constructores, siempre puede agregar reglas de ejecución verificación del tiempo en el constructor de la clase base mediante reflexión.

Richard

No estoy seguro de lo que está tratando de lograr, ¿podría explicarlo? La única razón para forzar un constructor específico o un método estático a través de diferentes clases es intentar ejecutarlos dinámicamente en tiempo de ejecución, ¿es correcto?

Se pretende que un constructor sea específico para una clase en particular, ya que está destinado a inicializar las necesidades específicas de la clase. Como lo entiendo, la razón por la que desearía imponer algo en una jerarquía o interfaz de clase, es que es una actividad / operación relevante para el proceso que se está realizando, pero puede variar en diferentes circunstancias. Creo que este es el beneficio previsto del polimorfismo, que no se puede lograr utilizando métodos estáticos.

También requeriría conocer el tipo específico de la clase a la que deseaba llamar el método estático, lo que rompería toda la ocultación polimórfica de las diferencias en el comportamiento que la interfaz o clase abstracta está tratando de lograr.

Si se pretende que el comportamiento representado por el constructor sea parte del contrato entre el cliente de estas clases, entonces lo agregaría explícitamente a la interfaz.

Si una jerarquía de clases tiene requisitos de inicialización similares, usaría una clase base abstracta, sin embargo, debería corresponder a las clases hereditarias la forma en que encuentran el parámetro para ese constructor, que puede incluir la exposición de un constructor similar o idéntico. / p>

Si se pretende que esto le permita crear diferentes instancias en tiempo de ejecución, recomendaría usar un método estático en una clase base abstracta que conozca las diferentes necesidades de todas las clases concretas (para esto podría usar la inyección de dependencia) .

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