modèle d'usine en C #: Comment assurer une instance d'objet ne peut être créé par une classe d'usine?

StackOverflow https://stackoverflow.com/questions/515269

Question

Récemment, j'ai pensé à assurer une partie de mon code. Je suis curieux de voir comment on pourrait faire en sorte un objet ne peut jamais être créé directement, mais seulement par une méthode d'une classe d'usine. Disons que j'ai une certaine classe « objet métier » et je veux vous assurer que toute instance de cette classe aura un état interne valide. Pour y parvenir, je devrai effectuer une vérification avant de créer un objet, probablement dans son constructeur. Tout cela est bien jusqu'à ce que je décide que je veux faire de ce chèque soit une partie de la logique métier. Alors, comment puis-je prendre pour un objet métier pour être creatable que par une méthode dans ma classe de logique métier, mais jamais directement? Le premier désir naturel d'utiliser un bon vieux mot-clé « ami » de C ++ échouerons avec C #. Nous avons donc besoin d'autres options ...

Essayons quelques exemples:

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    public MyBusinessObjectClass (string myProperty)
    {
        MyProperty = myProperty;
    }
}

public MyBusinessLogicClass
{
    public MyBusinessObjectClass CreateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

Il est tout va bien jusqu'à ce que vous vous souvenez, vous pouvez toujours créer une instance MyBusinessObjectClass directement, sans vérifier l'entrée. Je voudrais exclure cette possibilité technique tout à fait.

Alors, qu'est-ce que la communauté pense à ce sujet?

Était-ce utile?

La solution

On dirait que vous voulez juste pour exécuter une logique métier avant de créer l'objet - alors pourquoi ne pas vous venez de créer une méthode statique à l'intérieur du « BusinessClass » qui fait tout le travail de « myProperty » sale vérification et faire le constructeur privé

public BusinessClass
{
    public string MyProperty { get; private set; }

    private BusinessClass()
    {
    }

    private BusinessClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    public static BusinessClass CreateObject(string myProperty)
    {
        // Perform some check on myProperty

        if (/* all ok */)
            return new BusinessClass(myProperty);

        return null;
    }
}

Calling il serait assez simple:

BusinessClass objBusiness = BusinessClass.CreateObject(someProperty);

Autres conseils

Vous pouvez faire le constructeur privé, et l'usine d'un type imbriqué:

public class BusinessObject
{
    private BusinessObject(string property)
    {
    }

    public class Factory
    {
        public static BusinessObject CreateBusinessObject(string property)
        {
            return new BusinessObject(property);
        }
    }
}

Cela fonctionne parce que les types imbriqués ont accès aux membres privés de leurs types enserrant. Je sais qu'il est un peu restrictif, mais nous espérons que ça va aider ...

Ou, si vous voulez aller vraiment de fantaisie, inverser le contrôle. Demandez à la classe de retour de l'usine, et l'instrument de l'usine avec un délégué qui peut créer la classe

public class BusinessObject
{
  public static BusinessObjectFactory GetFactory()
  {
    return new BusinessObjectFactory (p => new BusinessObject (p));
  }

  private BusinessObject(string property)
  {
  }
}

public class BusinessObjectFactory
{
  private Func<string, BusinessObject> _ctorCaller;

  public BusinessObjectFactory (Func<string, BusinessObject> ctorCaller)
  {
    _ctorCaller = ctorCaller;
  }

  public BusinessObject CreateBusinessObject(string myProperty)
  {
    if (...)
      return _ctorCaller (myProperty);
    else
      return null;
  }
}

:)

Vous pouvez faire le constructeur de votre classe MyBusinessObjectClass interne, et le déplacer et l'usine dans leur propre assemblée. Maintenant que l'usine devrait être en mesure de construire une instance de la classe.

En dehors de ce que Jon a suggéré, vous pouvez aussi avoir soit la méthode d'usine (y compris le contrôle) est une méthode statique de BusinessObject en premier lieu. Ensuite, ont le constructeur privé, et tout le monde sera obligé d'utiliser la méthode statique.

public class BusinessObject
{
  public static Create (string myProperty)
  {
    if (...)
      return new BusinessObject (myProperty);
    else
      return null;
  }
}

Mais la vraie question est - pourquoi avez-vous cette exigence? Est-il acceptable pour déplacer l'usine ou la méthode d'usine dans la classe?

Une autre option (légère) est de faire une méthode d'usine statique dans la classe BusinessObject et garder le constructeur privé.

public class BusinessObject
{
    public static BusinessObject NewBusinessObject(string property)
    {
        return new BusinessObject();
    }

    private BusinessObject()
    {
    }
}

Après tant d'années cela a demandé, et toutes les réponses que je vois sont malheureusement vous dire comment vous devez faire votre code au lieu de donner une réponse directe. La réponse réelle que vous recherchez est d'avoir vos cours avec un constructeur privé, mais une instantiateur publique, ce qui signifie que vous ne pouvez créer de nouvelles instances d'autres instances existantes ... qui ne sont disponibles dans l'usine:

L'interface pour vos classes:

public interface FactoryObject
{
    FactoryObject Instantiate();
}

Votre classe:

public class YourClass : FactoryObject
{
    static YourClass()
    {
        Factory.RegisterType(new YourClass());
    }

    private YourClass() {}

    FactoryObject FactoryObject.Instantiate()
    {
        return new YourClass();
    }
}

Et enfin, l'usine:

public static class Factory
{
    private static List<FactoryObject> knownObjects = new List<FactoryObject>();

    public static void RegisterType(FactoryObject obj)
    {
        knownObjects.Add(obj);
    }

    public static T Instantiate<T>() where T : FactoryObject
    {
        var knownObject = knownObjects.Where(x => x.GetType() == typeof(T));
        return (T)knownObject.Instantiate();
    }
}

Ensuite, vous pouvez facilement modifier ce code si vous avez besoin des paramètres supplémentaires pour l'instanciation ou à prétraiter les instances que vous créez. Et ce code vous permettra de forcer l'instanciation dans l'usine que le constructeur de la classe est privée.

Alors, il ressemble à ce que je veux ne peut pas être fait d'une manière « pure ». Il est toujours une sorte de « call back » à la classe logique.

Peut-être que je pourrais le faire d'une manière simple, il suffit de faire une méthode contructor dans la classe d'objets d'abord appeler la classe logique pour vérifier l'entrée?

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass (string myProperty)
    {
        MyProperty  = myProperty;
    }

    pubilc static MyBusinessObjectClass CreateInstance (string myProperty)
    {
        if (MyBusinessLogicClass.ValidateBusinessObject (myProperty)) return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

public MyBusinessLogicClass
{
    public static bool ValidateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        return CheckResult;
    }
}

De cette façon, l'objet d'affaires ne sont pas directement creatable et la méthode de contrôle public dans la logique métier fera pas de mal non plus.

Dans un cas de bonne séparation entre les interfaces et les implémentations des
modèle constructeur protégé public-initialiseur permet une solution très soignée.

Étant donné un objet métier:

public interface IBusinessObject { }

class BusinessObject : IBusinessObject
{
    public static IBusinessObject New() 
    {
        return new BusinessObject();
    }

    protected BusinessObject() 
    { ... }
}

et une usine d'affaires:

public interface IBusinessFactory { }

class BusinessFactory : IBusinessFactory
{
    public static IBusinessFactory New() 
    {
        return new BusinessFactory();
    }

    protected BusinessFactory() 
    { ... }
}

la modification suivante initialiseur donne la BusinessObject.New() solution

class BusinessObject : IBusinessObject
{
    public static IBusinessObject New(BusinessFactory factory) 
    { ... }

    ...
}

Voici une référence à l'usine d'affaires de béton est nécessaire pour appeler l'initialiseur BusinessObject. Mais le seul qui a la référence requise est elle-même usine d'affaires.

Nous avons obtenu ce que nous voulions. Le seul qui peut créer est BusinessFactory <=>

    public class HandlerFactory: Handler
    {
        public IHandler GetHandler()
        {
            return base.CreateMe();
        }
    }

    public interface IHandler
    {
        void DoWork();
    }

    public class Handler : IHandler
    {
        public void DoWork()
        {
            Console.WriteLine("hander doing work");
        }

        protected IHandler CreateMe()
        {
            return new Handler();
        }

        protected Handler(){}
    }

    public static void Main(string[] args)
    {
        // Handler handler = new Handler();         - this will error out!
        var factory = new HandlerFactory();
        var handler = factory.GetHandler();

        handler.DoWork();           // this works!
    }

Je mettrais l'usine dans le même ensemble que la classe de domaine, et la marque constructeur de la classe de domaine interne. Cette façon toute classe dans votre domaine peut être en mesure de créer une instance, mais vous vous faites pas confiance, non? Tout le monde à écrire du code en dehors de la couche de domaine devra utiliser votre usine.

public class Person
{
  internal Person()
  {
  }
}

public class PersonFactory
{
  public Person Create()
  {
    return new Person();
  }  
}

Cependant, je dois remettre en question votre approche: -)

Je pense que si vous voulez que votre classe Person soit valide lors de la création, vous devez mettre le code du constructeur.

public class Person
{
  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
    Validate();
  }
}

Cette solution est basé sur idée de munificents d'utiliser un jeton dans le constructeur. Fait dans cette faire l'objet que seulement créé par l'usine (C #)

  public class BusinessObject
    {
        public BusinessObject(object instantiator)
        {
            if (instantiator.GetType() != typeof(Factory))
                throw new ArgumentException("Instantiator class must be Factory");
        }

    }

    public class Factory
    {
        public BusinessObject CreateBusinessObject()
        {
            return new BusinessObject(this);
        }
    }

Des approches multiples avec différents compromis ont été mentionnés.

  • Nesting la classe usine de la classe privée construite permet que l'usine de construire 1 classe. À ce moment-là, vous êtes mieux avec une méthode et un cteur Create privé.
  • Utilisation de l'héritage et une cteur protégée a le même problème.

Je voudrais proposer l'usine en tant que classe partielle qui contient des classes imbriquées privées avec les constructeurs publics. Vous êtes 100% cacher l'objet est de construire et d'exposer seulement votre usine ce que vous choisissez par une ou plusieurs interfaces.

Le cas d'utilisation, j'ai entendu pour cela quand vous voulez suivre 100% des cas dans l'usine. Cette conception garantit pas une, mais l'usine a accès à la création d'instances de « produits chimiques » définis dans la « usine » et il supprime la nécessité d'un ensemble séparé pour y parvenir.

== ChemicalFactory.cs ==
partial class ChemicalFactory {
    private  ChemicalFactory() {}

    public interface IChemical {
        int AtomicNumber { get; }
    }

    public static IChemical CreateOxygen() {
        return new Oxygen();
    }
}


== Oxygen.cs ==
partial class ChemicalFactory {
    private class Oxygen : IChemical {
        public Oxygen() {
            AtomicNumber = 8;
        }
        public int AtomicNumber { get; }
    }
}



== Program.cs ==
class Program {
    static void Main(string[] args) {
        var ox = ChemicalFactory.CreateOxygen();
        Console.WriteLine(ox.AtomicNumber);
    }
}

Je ne comprends pas pourquoi vous voulez séparer la « logique métier » du « objet métier ». Cela ressemble à une distorsion de l'orientation de l'objet, et vous finirez par vous attacher en nœuds en prenant cette approche.

Je ne pense pas qu'il y ait une solution qui n'est pas pire que le problème, tout ce qu'il faut par-dessus une usine statique publique qui à mon humble avis est un problème pire et d'arrêter l'habitude des gens appeler l'usine à utiliser votre objet - il ne marche pas cacher quoi que ce soit . Meilleur pour exposer une interface et / ou garder le constructeur comme interne si vous pouvez c'est la meilleure protection puisque l'ensemble est le code de confiance.

L'une des options est d'avoir un constructeur statique qui enregistre une usine quelque part avec quelque chose comme un conteneur du CIO.

Voici une autre solution dans la veine de « juste parce que vous pouvez ne signifie pas que vous devriez » ...

Il ne répond aux exigences de maintien de la constructeur privé d'objets métier et de mettre la logique de l'usine dans une autre classe. Après cela, il obtient un peu louche.

La classe usine a une méthode statique pour créer des objets métier. Il dérive de la classe d'objet métier afin d'accéder à une méthode de construction statique protégée qui appelle le constructeur privé.

L'usine est abstraite de sorte que vous ne pouvez pas réellement créer une instance de celui-ci (parce qu'il serait également un objet métier, de sorte que ce serait bizarre), et il a un constructeur privé si le code client ne peut pas en tirer .

Qu'est-ce qui est pas empêché code client aussi dérivant de la classe d'objets métier et appeler la méthode de construction statique protégée (mais non validé). Ou pire, appeler le constructeur par défaut protégé nous avons dû ajouter pour obtenir la classe d'usine pour compiler en premier lieu. (Ce qui est d'ailleurs susceptible d'être un problème avec un modèle qui sépare la classe d'usine de la classe d'objets métier.)

Je ne suis pas en train de suggérer quelqu'un dans leur esprit devrait faire quelque chose comme ça, mais il était un exercice intéressant. FWIW, ma solution préférée serait d'utiliser un constructeur interne et la limite de montage que la garde.

using System;

public class MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    // Need accesible default constructor, or else MyBusinessObjectFactory declaration will generate:
    // error CS0122: 'MyBusinessObjectClass.MyBusinessObjectClass(string)' is inaccessible due to its protection level
    protected MyBusinessObjectClass()
    {
    }

    protected static MyBusinessObjectClass Construct(string myProperty)
    {
        return new MyBusinessObjectClass(myProperty);
    }
}

public abstract class MyBusinessObjectFactory : MyBusinessObjectClass
{
    public static MyBusinessObjectClass CreateBusinessObject(string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return Construct(myProperty);

        return null;
    }

    private MyBusinessObjectFactory()
    {
    }
}

Apprécierait entendre quelques réflexions sur cette solution. Le seul capable de créer « MyClassPrivilegeKey » est l'usine. et « MyClass » il faut dans le constructeur. Évitant ainsi la réflexion sur des entrepreneurs privés / « inscription » à l'usine.

public static class Runnable
{
    public static void Run()
    {
        MyClass myClass = MyClassPrivilegeKey.MyClassFactory.GetInstance();
    }
}

public abstract class MyClass
{
    public MyClass(MyClassPrivilegeKey key) { }
}

public class MyClassA : MyClass
{
    public MyClassA(MyClassPrivilegeKey key) : base(key) { }
}

public class MyClassB : MyClass
{
    public MyClassB(MyClassPrivilegeKey key) : base(key) { }
}


public class MyClassPrivilegeKey
{
    private MyClassPrivilegeKey()
    {
    }

    public static class MyClassFactory
    {
        private static MyClassPrivilegeKey key = new MyClassPrivilegeKey();

        public static MyClass GetInstance()
        {
            if (/* some things == */true)
            {
                return new MyClassA(key);
            }
            else
            {
                return new MyClassB(key);
            }
        }
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top