Factory pattern in C#: How to ensure an object instance can only be created by a factory class?

后端 未结 17 1510
小鲜肉
小鲜肉 2020-11-29 16:51

Recently I\'ve been thinking about securing some of my code. I\'m curious how one could make sure an object can never be created directly, but only via some method of a fact

相关标签:
17条回答
  • 2020-11-29 17:04

    Multiple approaches with different tradeoffs have been mentioned.

    • Nesting the factory class in the privately constructed class only allows the factory to construct 1 class. At that point you're better off with a Create method and a private ctor.
    • Using inheritance and a protected ctor has the same issue.

    I'd like to propose the factory as a partial class that contains private nested classes with public constructors. You're 100% hiding the object your factory is constructing and only exposing what you choose to through one or multiple interfaces.

    The use case I heard for this would be when you want to track 100% of instances in the factory. This design guarantees no one but the factory has access to creating instances of "chemicals" defined in the "factory" and it removes the need for a separate assembly to achieve that.

    == 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);
        }
    }
    
    0 讨论(0)
  • 2020-11-29 17:04

    Here is another solution in the vein of "just because you can doesn't mean you should" ...

    It does meet the requirements of keeping the business object constructor private and putting the factory logic in another class. After that it gets a bit sketchy.

    The factory class has a static method for creating business objects. It derives from the business object class in order to access a static protected construction method that invokes the private constructor.

    The factory is abstract so you can't actually create an instance of it (because it would also be a business object, so that would be weird), and it has a private constructor so client code can't derive from it.

    What's not prevented is client code also deriving from the business object class and calling the protected (but unvalidated) static construction method. Or worse, calling the protected default constructor we had to add to get the factory class to compile in the first place. (Which incidentally is likely to be a problem with any pattern that separates the factory class from the business object class.)

    I'm not trying to suggest anyone in their right mind should do something like this, but it was an interesting exercise. FWIW, my preferred solution would be to use an internal constructor and the assembly boundary as the guard.

    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()
        {
        }
    }
    
    0 讨论(0)
  • 2020-11-29 17:05

    Looks like you just want to run some business logic before creating the object - so why dont you just create a static method inside the "BusinessClass" that does all the dirty "myProperty" checking work, and make the constructor private?

    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 it would be pretty straightforward:

    BusinessClass objBusiness = BusinessClass.CreateObject(someProperty);
    
    0 讨论(0)
  • 2020-11-29 17:05

    You could make the constructor on your MyBusinessObjectClass class internal, and move it and the factory into their own assembly. Now only the factory should be able to construct an instance of the class.

    0 讨论(0)
  • 2020-11-29 17:05

    I don't understand why you want to separate the "business logic" from the "business object". This sounds like a distortion of object orientation, and you'll end up tying yourself in knots by taking that approach.

    0 讨论(0)
  • 2020-11-29 17:07

    This solution is based off munificents idea of using a token in the constructor. Done in this answer make sure object only created by factory (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);
            }
        }
    
    0 讨论(0)
提交回复
热议问题