web-dev-qa-db-fra.com

Quelle est la bonne alternative à l'héritage de méthode statique?

Je comprends que l'héritage de méthode statique n'est pas pris en charge en C #. J'ai également lu un certain nombre de discussions (y compris ici) dans lesquelles les développeurs affirment avoir besoin de cette fonctionnalité, à laquelle la réponse typique est "si vous avez besoin d'un héritage de membre statique, il y a une faille dans votre conception".

OK, étant donné que OOP ne veut même pas que je pense à l'héritage statique, je dois en conclure que mon besoin apparent indique une erreur dans ma conception. Mais, je suis coincé. J'apprécierais vraiment de l'aide pour résoudre ce problème. Voici le défi ...

Je veux créer une classe de base abstraite (appelons-la Fruit) qui encapsule un code d'initialisation complexe. Ce code ne peut pas être placé dans le constructeur, car certains s'appuieront sur des appels de méthode virtuelle.

Fruit sera hérité par d'autres classes concrètes (Apple, Orange), dont chacune doit exposer une méthode d'usine standard CreateInstance () pour créer et initialiser une instance.

Si l'héritage des membres statiques était possible, je placerais la méthode d'usine dans la classe de base et utiliserais un appel de méthode virtuelle à la classe dérivée pour obtenir le type à partir duquel une instance concrète doit être initialisée. Le code client invoquerait simplement Apple.CreateInstance () pour obtenir une instance Apple Apple entièrement initialisée).

Mais ce n'est clairement pas possible, alors quelqu'un peut-il expliquer comment ma conception doit changer pour s'adapter aux mêmes fonctionnalités.

78
Tim Coulter

Une idée:

public abstract class Fruit<T>
    where T : Fruit<T>, new()
{
    public static T CreateInstance()
    {
        T newFruit = new T();
        newFruit.Initialize();  // Calls Apple.Initialize
        return newFruit;
    }

    protected abstract void Initialize();
}

public class Apple : Fruit<Apple>
{
    protected override void Initialize() { ... }
}

Et appelez comme ça:

Apple myAppleVar = Fruit<Apple>.CreateInstance();

Aucune classe d'usine supplémentaire n'est nécessaire.

57
Matt Hamsmith

Déplacez la méthode d'usine hors du type et placez-la dans sa propre classe Factory.

public abstract class Fruit
{
    protected Fruit() {}

    public abstract string Define();

}

public class Apple : Fruit
{
    public Apple() {}

    public override string Define()
    {
         return "Apple";
    }
}

public class Orange : Fruit
{
    public Orange() {}

    public override string Define()
    {
         return "Orange";
    }
}

public static class FruitFactory<T> 
{
     public static T CreateFruit<T>() where T : Fruit, new()
     {
         return new T();
     }
}

Mais, comme je regarde cela, il n'est pas nécessaire de déplacer la méthode Create vers sa propre classe Factory (bien que je pense que c'est préférable -séparation des préoccupations-), vous pouvez la mettre dans la classe Fruit:

public abstract class Fruit
{

   public abstract string Define();

   public static T CreateFruit<T>() where T : Fruit, new()
   {
        return new T();
   }

}

Et pour voir si ça marche:

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine (Fruit.CreateFruit<Apple> ().Define ());
            Console.WriteLine (Fruit.CreateFruit<Orange> ().Define ());

            Console.ReadLine ();
        }        
    }
16
Frederik Gheysels

Pourquoi ne pas créer une classe d'usine (basée sur des modèles) avec une méthode create?

FruitFactory<Banana>.Create();
4
Daniel Rodriguez

Je ferais quelque chose comme ça

 public abstract class Fruit() {
      public abstract void Initialize();
 }

 public class Apple() : Fruit {
     public override void Initialize() {

     }
 }

 public class FruitFactory<T> where T : Fruit, new {
      public static <T> CreateInstance<T>() {
          T fruit = new T();
          fruit.Initialize();
          return fruit;  
      }
 } 


var fruit = FruitFactory<Apple>.CreateInstance()
4
Bob

La classe WebRequest et ses types dérivés dans le .NET BCL représentent un bon exemple de la façon dont ce type de conception peut être implémenté relativement bien.

La classe WebRequest a plusieurs sous-classes, dont HttpWebRequest et FtpWebReuest. Maintenant, cette classe de base WebRequest est également un type de fabrique et expose une méthode statique Create (les constructeurs d'instance sont masqués, comme requis par le modèle de fabrique).

public static WebRequest Create(string requestUriString)
public static WebRequest Create(Uri requestUri)

Cette méthode Create renvoie une implémentation spécifique de la classe WebRequest et utilise l'URI (ou la chaîne URI) pour déterminer le type d'objet à créer et à renvoyer.

Cela a pour résultat final le modèle d'utilisation suivant:

var httpRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com/");
// or equivalently
var httpRequest = (HttpWebRequest)HttpWebWebRequest.Create("http://stackoverflow.com/");

var ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://stackoverflow.com/");
// or equivalently
var ftpRequest = (FtpWebRequest)FtpWebWebRequest.Create("ftp://stackoverflow.com/");

Personnellement, je pense que c'est une bonne façon d'aborder le problème, et cela semble en effet être la méthode préférée des créateurs de .NET Framework.

3
Noldorin

Tout d'abord, ne pas avoir d'initialiseurs statiques qui peuvent être virtuels ne signifie pas que vous ne pouvez pas avoir de méthodes membres "standard", qui pourraient être surchargées. Deuxièmement, vous pouvez appeler vos méthodes virtuelles à partir de constructeurs, et elles fonctionneront comme prévu, donc il n'y a pas de problème ici. Troisièmement, vous pouvez utiliser des génériques pour avoir une usine de type sécurisé.
.


abstract class Fruit
{
    public Fruit()
    {
        Initialize();
    }

    protected virtual void Initialize()
    {
        Console.WriteLine("Fruit.Initialize");
    }
}

class Apple : Fruit
{
    public Apple()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Apple.Initialize");
    }

    public override string ToString()
    {
        return "Apple";
    }
}

class Orange : Fruit
{
    public Orange()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Orange.Initialize");
    }

    public override string ToString()
    {
        return "Orange";
    }
}

class FruitFactory
{
    public static T CreateFruit<T>() where T : Fruit, new()
    {
        return new T();
    }
}

public class Program
{

    static void Main()
    {
        Apple apple = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(Apple.ToString());

        Orange orange = new Orange();
        Console.WriteLine(orange.ToString());

        Fruit appleFruit = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(appleFruit.ToString());
    }
}
3
Marcin Deptuła

Je dirais que la meilleure chose à faire est de créer une méthode d'initialisation virtuelle/abstraite sur la classe fruit qui doit être appelée, puis de créer une classe `` fabrique de fruits '' externe pour créer des instances:


public class Fruit
{
    //other members...
    public abstract void Initialise();
}

public class FruitFactory()
{
    public Fruit CreateInstance()
    {
        Fruit f = //decide which fruit to create
        f.Initialise();

        return f;
    }
}
0
Lee