2008-12-12 10 views
3

Est-ce une bonne idée d'avoir une classe de fabrique utilisant des génériques pour instancier des objets?Est-ce une bonne idée d'avoir une classe de fabrique utilisant des génériques pour instancier des objets?

Disons que j'ai un animal de classe et des sous-classes (chat, chien, etc.):

abstract class Animal 
{ 
    public abstract void MakeSound(); 
} 

class Cat : Animal 
{ 
    public override void MakeSound() 
    { 
     Console.Write("Mew mew"); 
    } 
} 

class Dog : Animal 
{ 
    public override void MakeSound() 
    { 
     Console.Write("Woof woof"); 
    } 
} 

static class AnimalFactory 
{ 
    public static T Create<T>() where T : Animal, new() 
    { 
     return new T(); 
    } 
} 

Puis dans mon code je voudrais utiliser AnimalFactory comme ceci:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Dog d = AnimalFactory.Create<Dog>(); 
     d.MakeSound(); 
    } 
} 
+0

Qu'est-ce que vous espérez gagner sur TBE façon simple: programme classe { static void Main (string [] args Les) { chien d = nouveau chien(); d.MakeSound(); } } – Anthony

+0

Une usine est utile lorsque vous ne savez pas avant l'exécution quel animal vous allez créer. Supposons que votre programme prenne le nom de la classe en tant qu'entrée et le transforme en type ... mais une instance de type n'est pas le nom de type que vous mettez dans . –

Répondre

4

dépend de ce que tu veux faire avec. L'exemple que vous avez donné n'est pas très utile, car tout ce qu'il fait est simplement appeler new(), ce que vous pouvez faire dans votre code. Une fabrique est plus utile si vous voulez placer une logique qui doit être exécutée pendant le processus de création d'objet quelque part en dehors de l'objet à créer, mais aussi à l'écart du code qui instancie les objets. Dans cette optique, cela dépend de la logique que vous voulez exécuter dans votre usine et si cette logique est également accessible en écriture en utilisant les mêmes contraintes génériques que l'usine. Si ce n'est pas le cas, vous aurez probablement besoin d'un modèle appelé Usine abstraite, qui utilise une fabrique générale pour instancier l'usine spécifique appropriée en arrière-plan afin que le type crée l'objet à portée de main.

3

Oui, les méthodes d'usine dans les classes non génériques peuvent souvent être utiles. En particulier, les méthodes génériques peuvent faire l'objet d'une inférence de type, alors que les génériques ne le peuvent pas.

j'ai parfois deux couches d'usines, trop - pour un type générique avec 2 paramètres de type, je pourrais avoir:

class Foo 
{ 
    static Foo<TKey, TValue> Create<TKey, TValue>(TKey key, TValue value) 
    {...} 
} 

class Foo<TKey> 
{ 
    static Foo<TKey, TValue> Create<TValue>(TValue value) 
    {...} 
} 

class Foo<TKey, TValue> 
{ 
} 

Cela permet:

Foo.Create(x, y); 
Foo<string>.Create(y); 
new Foo<string, int>(x, y); 

j'ai un blog entry plus exemples et raisonnement.

+0

Donc, en réponse à la question, est-ce que nous disons que c'est une bonne idée? Si oui, est-ce aussi une bonne idée d'avoir une seule usine globale qui crée un objet? – saj

+0

@saj: Oui, c'est bien d'avoir des méthodes d'usine ... mais non, avoir une seule classe d'usine globale me semble être une très mauvaise idée - ça finirait par la logique pour toutes sortes de classes différentes là-dedans. –

+0

L'exemple GenericFactory (https://designingefficientsoftware.wordpress.com/2014/10/02/generic-class-factory-c-part-3-4/) est donc une mauvaise idée. – saj

0

L'utilisation de génériques pour votre constructeur s'appelle le modèle d'usine abstrait.

C'est bon, mais seulement si vous l'utilisez, dans cet exemple, vous avez au moins quelques-unes des valeurs par défaut dans l'usine.

static class AnimalFactory 
{ 
    public static Animal Create<T>() where T : Animal 
    { 
     return Create<T>("blue"); 
    } 

    public static Animal Create<T>(string colour) where T : Animal, new() 
    { 
     return new T() {Colour = colour}; 
    } 
} 
1

L'une des choses plus ennuyeuses qui viennent de cela, et il pourrait y avoir un moyen de contourner cela, est que vous êtes coincé avec l'aide d'un constructeur sans paramètre. Autant que je sache, vous ne pouvez pas le faire:

public class Factory 
{ 
    public static T Create<T>() where T : ParentClass, new(String) 
    { 

    } 
} 

Ou cette

public class Factory 
{ 
    public static T Create<T>() where T : ParentClass 
    { 
    T child = new T("hi") 
    } 
} 

Ou encore plus amusant comme:

public class Factory 
{ 
    public static T Create<T>() where T : ParentClass, new() 
    { 
    T child; 

    child = new T(); 

    if (child is ChildClass) 
    { 
     child = new ChildClass("hi"); 
    } 

    return child; 
    } 
} 

qui peut vous limiter à certains conception de classe. Cela peut poser un problème si vous ne voulez pas que le constructeur par défaut soit appelé. C'est à moins que quelqu'un n'ait trouvé un moyen de contourner ce problème.

Je sais que j'ai rencontré ce problème avec Activator.CreateInstance(), car en réalité, tout ce que cela fait, c'est appeler le constructeur sans paramètre.

0

Est-ce une bonne idée d'avoir une classe usine utilisant des génériques instancier objets?

Pour le cas que vous avez décrit, non, ce n'est pas le cas. Vous utiliseriez simplement un motif pour utiliser un motif et, à partir de l'exemple, vous ne compileriez que des choses inutiles.

Maintenant, si votre application a plusieurs objets implémentant une interface et que vous chargez un de ces objets en fonction d'un fichier de configuration, alors oui, il est logique d'utiliser une fabrique pour créer cet objet.

La partie générique de ce problème viendrait réellement dans l'implémentation d'usine elle-même. Je n'ai pas de code de soutien, mais voici comment j'envisager le code appelant à regarder:

Factory<IAwesome> awesomeFactory = new Factory<IAwesome>(); 
IAwesome awesomeObject = awesomeFactory.Load(configValue); 
1

je recommande la séparation de la préoccupation de l'usine de la préoccupation du constructeur. Cela permet de faire varier les stratégies d'instanciation:

public interface IFactory<T> 
{ 
    T Create(); 
} 

public class DefaultConstructorFactory<T> : IFactory<T> where T : new() 
{ 
    public T Create() 
    { 
     return new T(); 
    } 
} 

public class ActivatorFactory<T> : IFactory<T> 
{ 
    public T Create() 
    { 
     return Activator.CreateInstance<T>(); 
    } 
} 

public class AnimalTamer<TAnimal> where TAnimal : Animal 
{ 
    IFactory<TAnimal> _animalFactory; 

    public AnimalTamer(IFactory<TAnimal> animalFactory) 
    { 
     if(animalFactory == null) 
     { 
      throw new ArgumentNullException("animalFactory"); 
     } 

     _animalFactory= animalFactory; 
    } 

    public void PutOnShow() 
    { 
     var animal = _animalFactory.Create(); 

     animal.MakeSound(); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var tamer = new AnimalTamer<Tiger>(new DefaultConstructorFactory<Tiger>()); 

     tamer.PutOnShow(); 
    } 
} 
0

oui. les modèles de conception de création (http://en.wikipedia.org) fonctionnent bien avec des génériques. et les génériques peuvent être un peu compliqués (http://www.artima.com/intv/genericsP.html), donc c'est bien de cacher toute complexité de construction dans une sorte d'usine pour le rendre plus facile pour le client.

1

Vous pouvez le rendre encore plus dynamique en utilisant open/close génériques et funcs quand rien ou très peu est connu jusqu'à l'exécution. On dirait que vous avez une bonne compréhension des génériques alors continuez comme ça.