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

最近我一直在考虑保护我的一些代码。我很好奇,如何才能确保永远不能直接创建对象,而只能通过工厂类的某种方法来创建。假设我有一些“业务对象”类,我想确保这个类的任何实例都具有有效的内部状态。为了实现这一点,我将需要执行一些检查之前,创建一个对象,可能在其构造函数。在我决定将此检查作为业务逻辑的一部分之前,这一切都没有问题。那么,如何安排业务对象只能通过业务逻辑类中的某个方法创建,而不能直接创建呢?使用 C + + 中的“朋友”关键字的第一个自然需求在 C # 中是无法实现的。所以我们需要其他选择。

我们来举个例子:

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

直到您记住您仍然可以直接创建 MyBusinessObjectClass 实例,而无需检查输入之前,一切都没有问题。我想完全排除这种技术可能性。

那么,社区对此有什么看法?

55564 次浏览

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.

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...

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. Then, have the constructor private, and everyone else will be forced to use the static method.

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?

Yet another (lightweight) option is to make a static factory method in the BusinessObject class and keep the constructor private.

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


private BusinessObject()
{
}
}

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

:)

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?

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.

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.

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.

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

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

In a case of good separation between interfaces and implementations the
protected-constructor-public-initializer pattern allows a very neat solution.

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:

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


...
}

Here a reference to concrete business factory is needed to call the BusinessObject.New() initializer. 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.

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.

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

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

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

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.

Would appreciate hearing some thoughts on this solution. The only one able to create 'MyClassPrivilegeKey' is the factory. and 'MyClass' requires it in the constructor. 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);
}
}
}
}