简体   繁体   English

C#中的工厂模式:如何确保对象实例只能由工厂类创建?

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

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 factory class. 我很好奇如何确保永远不能直接创建对象,而只能通过工厂类的某种方法创建。 Let us say I have some "business object" class and I want to make sure any instance of this class will have a valid internal state. 让我们说我有一些“业务对象”类,我想确保这个类的任何实例都有一个有效的内部状态。 In order to achieve this I will need to perform some check before creating an object, probably in its constructor. 为了实现这一点,我需要在创建对象之前执行一些检查,可能在其构造函数中。 This is all okay until I decide I want to make this check be a part of the business logic. 这一切都没关系,直到我决定将此检查作为业务逻辑的一部分。 So, how can I arrange for a business object to be creatable only through some method in my business logic class but never directly? 那么,我如何安排业务对象只能通过我的业务逻辑类中的某种方法创建,但从不直接? The first natural desire to use a good old "friend" keyword of C++ will fall short with C#. 使用C ++的旧“朋友”关键字的第一个自然愿望是C#无法实现。 So we need other options... 所以我们需要其他选择......

Let's try some example: 让我们试试一些例子:

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

It's all okay until you remember you can still create MyBusinessObjectClass instance directly, without checking the input. 这一切都没关系,直到你记得你仍然可以直接创建MyBusinessObjectClass实例,而无需检查输入。 I would like to exclude that technical possibility altogether. 我想完全排除这种技术可能性。

So, what does the community think about this? 那么,社区对此有何看法?

You can make the constructor private, and the factory a nested type: 您可以将构造函数设为私有,并将工厂设置为嵌套类型:

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

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

This works because nested types have access to the private members of their enclosing types. 这是有效的,因为嵌套类型可以访问其封闭类型的私有成员。 I know it's a bit restrictive, but hopefully it'll help... 我知道这有点限制,但希望它会有所帮助......

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? 看起来你只想在创建对象之前运行一些业务逻辑 - 那么为什么不在“BusinessClass”中创建一个静态方法来执行所有脏的“myProperty”检查工作,并使构造函数变为私有?

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

Or, if you want to go really fancy, invert control: Have the class return the factory, and instrument the factory with a delegate that can create the class. 或者,如果你想真正想要的话,反转控制:让班级返回工厂,并用可以创建班级的代表来检测工厂。

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

:) :)

You could make the constructor on your MyBusinessObjectClass class internal, and move it and the factory into their own assembly. 您可以将MyBusinessObjectClass类的构造函数设置为内部,并将它和工厂移动到它们自己的程序集中。 Now only the factory should be able to construct an instance of the class. 现在只有工厂应该能够构造一个类的实例。

Apart from what Jon suggested, you could also either have the factory method (including the check) be a static method of BusinessObject in the first place. 除了Jon建议之外,您还可以将工厂方法(包括检查)放在BusinessObject的静态方法中。 Then, have the constructor private, and everyone else will be forced to use the static method. 然后,将构造函数设置为private,并且其他所有人都将被强制使用静态方法。

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

But the real question is - why do you have this requirement? 但真正的问题是 - 为什么你有这个要求? Is it acceptable to move the factory or the factory method into the class? 将工厂或工厂方法移到课堂上是否可以接受?

After so many years this got asked, and all the answers I see are unfortunately telling you how you should do your code instead of giving a straight answer. 经过这么多年,我们被问到了,不幸的是,我看到的所有答案都告诉你应该如何处理你的代码,而不是给出一个直接的答案。 The actual answer you were looking for is having your classes with a private constructor but a public instantiator, meaning that you can only create new instances from other existing instances... that are only available in the factory: 您正在寻找的实际答案是让您的类具有私有构造函数但是公共实例化器,这意味着您只能从其他现有实例创建新实例...仅在工厂中可用:

The interface for your classes: 您的类的接口:

public interface FactoryObject
{
    FactoryObject Instantiate();
}

Your class: 你的班:

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

    private YourClass() {}

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

And, finally, the factory: 最后,工厂:

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

Then you can easily modify this code if you need extra parameters for the instantiation or to preprocess the instances you create. 然后,如果您需要实例化的额外参数或预处理您创建的实例,则可以轻松修改此代码。 And this code will allow you to force the instantiation through the factory as the class constructor is private. 此代码将允许您通过工厂强制实例化,因为类构造函数是私有的。

Yet another (lightweight) option is to make a static factory method in the BusinessObject class and keep the constructor private. 另一个(轻量级)选项是在BusinessObject类中创建一个静态工厂方法,并将构造函数保持为私有。

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

    private BusinessObject()
    {
    }
}
    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!
    }

So, it looks like what I want cannot be done in a "pure" way. 所以,看起来我想要的不能以“纯粹”的方式完成。 It's always some kind of "call back" to the logic class. 它始终是逻辑类的某种“回调”。

Maybe I could do it in a simple way, just make a contructor method in the object class first call the logic class to check the input? 也许我能以一种简单的方式做到这一点,只需在对象类中创建一个contructor方法,首先调用逻辑类来检查输入?

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

This way, the business object is not creatable directly and the public check method in business logic will do no harm either. 这样,业务对象不能直接创建,业务逻辑中的公共检查方法也不会造成任何伤害。

In a case of good separation between interfaces and implementations the 在接口和实现之间良好分离的情况下
protected-constructor-public-initializer pattern allows a very neat solution. protected-constructor-public-initializer模式允许非常简洁的解决方案。

Given a business object: 给定业务对象:

public interface IBusinessObject { }

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

    protected BusinessObject() 
    { ... }
}

and a business factory: 和一个商业工厂:

public interface IBusinessFactory { }

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

    protected BusinessFactory() 
    { ... }
}

the following change to BusinessObject.New() initializer gives the solution: BusinessObject.New()初始化程序的以下更改提供了解决方案:

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

    ...
}

Here a reference to concrete business factory is needed to call the BusinessObject.New() initializer. 这里需要对具体业务工厂的引用来调用BusinessObject.New()初始化程序。 But the only one who has the required reference is business factory itself. 但唯一拥有所需参考的人是商业工厂本身。

We got what we wanted: the only one who can create BusinessObject is BusinessFactory . 我们得到了我们想要的东西:唯一可以创建BusinessObjectBusinessFactory

This solution is based off munificents idea of using a token in the constructor. 这个解决方案基于munificents在构造函数中使用令牌的想法。 Done in this answer make sure object only created by factory (C#) 在此答案中完成确保对象仅由工厂创建(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);
        }
    }

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. 将工厂类嵌套在私有构造的类中只允许工厂构造1个类。 At that point you're better off with a Create method and a private ctor. 那时你最好使用Create方法和私人ctor。
  • Using inheritance and a protected ctor has the same issue. 使用继承和受保护的ctor具有相同的问题。

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. 您100%隐藏了工厂正在构建的对象,并且只通过一个或多个接口暴露您选择的对象。

The use case I heard for this would be when you want to track 100% of instances in the factory. 我听到的用例就是当你想跟踪工厂中100%的实例时。 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);
    }
}

I'd put the factory in the same assembly as the domain class, and mark the domain class's constructor internal. 我将工厂放在与域类相同的程序集中,并将域类的构造函数标记为内部。 This way any class in your domain may be able to create an instance, but you trust yourself not to, right? 这样你域中的任何类都可以创建一个实例,但你相信自己不会,对吧? Anyone writing code outside of the domain layer will have to use your factory. 任何在域层之外编写代码的人都必须使用您的工厂。

public class Person
{
  internal Person()
  {
  }
}

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

However, I must question your approach :-) 但是,我必须质疑你的方法:-)

I think that if you want your Person class to be valid upon creation you must put the code in the constructor. 我认为如果你希望你的Person类在创建时有效,你必须将代码放在构造函数中。

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

I don't think there is a solution that's not worse than the problem , all he above require a public static factory which IMHO is a worse problem and wont stop people just calling the factory to use your object - it doesnt hide anything . 我不认为有一个解决方案并不比问题更糟糕,所有他上面都需要一个公共静态工厂,恕我直言是一个更糟糕的问题,不会阻止人们只是叫工厂使用你的对象 - 它不会隐藏任何东西。 Best to expose an interface and/or keep the constructor as internal if you can that's the best protection since the assembly is trusted code. 最好公开一个接口和/或将构造函数保持为内部,如果你能做到最好的保护,因为程序集是可信代码。

One option is to have a static constructor which registers a factory somewhere with something like an IOC container. 一种选择是有一个静态构造函数,它可以在某个地方注册一个类似IOC容器的工厂。

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. FWIW,我的首选解决方案是使用内部构造函数和程序集边界作为防护。

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

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. 这听起来像是一个面向对象的扭曲,你最终会采取这种方法将自己捆绑在一起。

Would appreciate hearing some thoughts on this solution. 希望听到有关此解决方案的一些想法。 The only one able to create 'MyClassPrivilegeKey' is the factory. 能够创建'MyClassPrivilegeKey'的唯一一个是工厂。 and 'MyClass' requires it in the constructor. 并且'MyClass'在构造函数中需要它。 Thus avoiding reflection on private contractors / "registration" to the factory. 因此,避免反映私人承包商/“注册”到工厂。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM