Question

Existe-t-il un moyen de forcer une classe (enfant) à avoir des constructeurs avec des signatures particulières ou des méthodes statiques particulières en C # ou en Java?

Vous ne pouvez évidemment pas utiliser d'interfaces pour cela, et je sais que son utilisation sera limitée. Je trouve cela utile, par exemple, lorsque vous souhaitez appliquer certaines directives de conception, par exemple:

Exceptions
Ils devraient tous avoir les quatre constructeurs canoniques, mais il n'y a aucun moyen de les appliquer. Vous devez vous fier à un outil tel que FxCop (cas C #) pour les détecter.

Opérateurs
Il n'y a pas de contrat spécifiant que deux classes peuvent être additionnées (avec l'opérateur + en C #)

Existe-t-il un modèle de conception permettant de contourner cette limitation? Quelle construction pourrait être ajoutée au langage pour surmonter cette limitation dans les futures versions de C # ou de Java?

Était-ce utile?

La solution

Non appliqué au moment de la compilation, mais j'ai passé beaucoup de temps à examiner des problèmes similaires; une bibliothèque mathématique activée par le générique , ainsi qu'une bibliothèque efficace -default) Les API ctor sont toutes deux disponibles dans MiscUtil . Cependant, ils ne sont vérifiés qu'à la première utilisation au moment de l'exécution. En réalité, ce n'est pas un gros problème - vos tests unitaires devraient détecter très rapidement tout opérateur / ctor manquant. Mais ça marche et très vite ...

Autres conseils

En utilisant des génériques, vous pouvez forcer un argument de type à avoir un constructeur sans paramètre - mais c'est à peu près sa limite.

Hormis dans les génériques, il serait difficile d'utiliser ces restrictions même si elles existaient, mais cela pourrait parfois être utile pour les paramètres / arguments de type. Autoriser des membres statiques dans des interfaces (ou éventuellement des interfaces statiques) pourrait également aider à utiliser l'opérateur "opérateur numérique générique". problème.

I a écrit à ce sujet il y a quelques instants devant un problème similaire.

Vous pouvez utiliser le modèle Factory.

interface Fruit{}

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

   Cocktail mixFruits(F f1,F f2);
}

Vous pouvez ensuite créer des classes pour tout type de fruit

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

Cela ne signifie pas que vous ne pouvez pas créer d'instance autrement qu'en utilisant Factory, mais vous pouvez au moins spécifier les méthodes que vous demanderiez à une Factory.

Forcer les constructeurs

Vous ne pouvez pas. Le plus proche que vous puissiez obtenir est de rendre le constructeur par défaut privé, puis de fournir un constructeur doté de paramètres. Mais il y a encore des échappatoires.

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
}

Le problème dans le langage est que les méthodes statiques sont vraiment des citoyens de seconde classe (un constructeur est aussi une sorte de méthode statique, car vous n'avez pas besoin d'une instance pour commencer).

Les méthodes statiques ne sont que des méthodes globales avec un espace de noms. Elles ne sont pas vraiment "appartiennent". à la classe dans laquelle ils sont définis (OK, ils ont accès aux méthodes privées (statiques) de la classe, mais c'est à peu près tout.)

Le problème au niveau du compilateur est que sans une instance de classe, vous n'avez pas de table de fonction virtuelle, ce qui signifie que vous ne pouvez pas utiliser tout le matériel d'héritage et de polymorphisme.

Je pense que cela pourrait fonctionner en ajoutant une table virtuelle globale / statique pour chaque classe, mais si cela n'a pas encore été fait, il y a probablement une bonne raison à cela.

Voilà, je le résoudrais si j'étais un concepteur de langage.

Autoriser les interfaces à inclure des méthodes, des opérateurs et des constructeurs statiques.

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

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

Ne pas autoriser le nouvel IFoo (someInt) ou IFoo.Bar () correspondant

Autoriser l'héritage des constructeurs (comme des méthodes statiques).

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

Autorise le programmeur à transtyper vers les interfaces et vérifie, si nécessaire, au moment de l'exécution si la conversion est sémantiquement valide même si la classe ne spécifie pas explicitement.

ISummable PassedFirstGrade = (ISummable) 10; 

Malheureusement, vous ne pouvez pas en C #. Voici un coup de poing cependant:

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

Eh bien, je sais d'après le libellé de votre question que vous souhaitez une exécution au moment de la compilation. À moins que quelqu'un d'autre ait une suggestion brillante / bidouille qui vous permettra de le faire de la façon dont vous impliquez le compilateur, je suggérerais que vous puissiez écrire une tâche MSbuild personnalisée qui l'a fait. Un framework AOP tel que PostSharp pourrait vous aider à accomplir cela au moment du jeu en vous appuyant sur son modèle de tâches de construction.

Mais qu'est-ce qui ne va pas avec l'analyse de code ou l'application au moment de l'exécution? C’est peut-être juste une préférence et je la respecte, mais je n’ai personnellement aucun problème à ce que CA / FXCop vérifie ces choses ... et si vous voulez vraiment forcer les implémenteurs en aval de vos classes à avoir des signatures de constructeur, vous pouvez toujours ajouter des règles. vérification du temps dans le constructeur de la classe de base en utilisant la réflexion.

Richard

Je ne suis pas sûr de ce que vous essayez d'atteindre. Pouvez-vous élaborer? La seule raison pour forcer un constructeur spécifique ou une méthode statique sur plusieurs classes est d’essayer de les exécuter de manière dynamique au moment de l’exécution, est-ce correct?

Un constructeur est destiné à être spécifique à une classe particulière, car il est destiné à initialiser les besoins spécifiques de la classe. Si je comprends bien, si vous souhaitez appliquer quelque chose dans une hiérarchie ou une interface de classe, c'est qu'il s'agit d'une activité / opération pertinente pour le processus en cours d'exécution, mais qui peut varier selon les circonstances. Je pense que c’est l’avantage escompté du polymorphisme, ce que vous ne pouvez pas obtenir avec des méthodes statiques.

Il faudrait également connaître le type spécifique de la classe pour laquelle vous souhaitez appeler la méthode statique, ce qui briserait toute la dissimulation polymorphe des différences de comportement que la classe d'interface ou abstraite tente d'obtenir.

Si le comportement représenté par le constructeur est destiné à faire partie du contrat entre le client de ces classes, je l'ajouterai explicitement à l'interface.

Si une hiérarchie de classes a des exigences d’initialisation similaires, j’utiliserais une classe de base abstraite. Toutefois, il appartient aux classes qui héritent de déterminer comment elles trouvent le paramètre de ce constructeur, ce qui peut inclure l’exposition d’un constructeur similaire ou identique.

Si ceci est destiné à vous permettre de créer différentes instances au moment de l'exécution, je vous recommande d'utiliser une méthode statique sur une classe de base abstraite qui connaît les différents besoins de toutes les classes concrètes (vous pouvez utiliser l'injection de dépendance pour cela). .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top