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

时间:2009-02-05 10:13:40

标签: c# design-patterns oop factory

最近我一直在考虑保护我的一些代码。我很好奇如何确保永远不能直接创建对象,而只能通过工厂类的某种方法创建。让我们说我有一些“业务对象”类,我想确保这个类的任何实例都有一个有效的内部状态。为了实现这一点,我需要在创建对象之前执行一些检查,可能在其构造函数中。这一切都没关系,直到我决定将此检查作为业务逻辑的一部分。那么,我如何安排业务对象只能通过我的业务逻辑类中的某种方法创建,但从不直接?使用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实例,而无需检查输入。我想完全排除这种技术可能性。

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

17 个答案:

答案 0 :(得分:60)

您可以将构造函数设为私有,并将工厂设置为嵌套类型:

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

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

这是有效的,因为嵌套类型可以访问其封闭类型的私有成员。我知道这有点限制,但希望它会有所帮助......

答案 1 :(得分:56)

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

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

调用它将非常简单:

BusinessClass objBusiness = BusinessClass.CreateObject(someProperty);

答案 2 :(得分:48)

或者,如果你想要真正看上去,反过来控制:让班级返回工厂,并使用可以创建班级的代表来检测工厂。

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

:)

答案 3 :(得分:15)

您可以将MyBusinessObjectClass类的构造函数设置为内部,并将它和工厂移动到它们自己的程序集中。现在只有工厂应该能够构造一个类的实例。

答案 4 :(得分:7)

除了Jon建议的内容之外,您还可以将工厂方法(包括检查)作为BusinessObject的静态方法。然后,将构造函数设置为private,其他所有人都将被强制使用静态方法。

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

但真正的问题是 - 为什么你有这个要求?将工厂或工厂方法移到班级是否可以接受?

答案 5 :(得分:4)

另一个(轻量级)选项是在BusinessObject类中创建一个静态工厂方法,并将构造函数保持为私有。

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

    private BusinessObject()
    {
    }
}

答案 6 :(得分:3)

经过这么多年的讨论,我得到了所有答案,不幸的是告诉你应该如何处理你的代码,而不是给出一个直接的答案。您正在寻找的实际答案是让您的类具有私有构造函数但是公共实例化器,这意味着您只能从其他现有实例创建新实例...仅在工厂中可用:

您班级的界面:

public interface FactoryObject
{
    FactoryObject Instantiate();
}

你的班级:

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

    private YourClass() {}

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

最后,工厂:

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

如果您需要额外的实例参数或预处理您创建的实例,则可以轻松修改此代码。此代码允许您通过工厂强制实例化,因为类构造函数是私有的。

答案 7 :(得分:2)

所以,看起来我想要的不能以“纯粹”的方式完成。它总是某种“回调”逻辑类。

也许我能以一种简单的方式做到这一点,只需在对象类中创建一个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;
    }
}

这样,业务对象不能直接创建,业务逻辑中的公共检查方法也不会造成伤害。

答案 8 :(得分:2)

在接口和实现之间良好分离的情况下 protected-constructor-public-initializer模式允许非常简洁的解决方案。

给定业务对象:

public interface IBusinessObject { }

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

    protected BusinessObject() 
    { ... }
}

和一家商业工厂:

public interface IBusinessFactory { }

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

    protected BusinessFactory() 
    { ... }
}

以下对BusinessObject.New()初始化程序的更改给出了解决方案:

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

    ...
}

此处需要引用具体的商业工厂来调用BusinessObject.New()初始值设定项。但唯一拥有所需参考资料的是商业工厂本身。

我们得到了我们想要的东西:唯一可以创建BusinessObject的人是BusinessFactory

答案 9 :(得分:2)

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

答案 10 :(得分:1)

我将工厂放在与域类相同的程序集中,并将域类的构造函数标记为内部。这样你域中的任何类都可以创建一个实例,但你相信自己不会,对吧?任何在域层之外编写代码的人都必须使用您的工厂。

public class Person
{
  internal Person()
  {
  }
}

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

但是,我必须质疑你的方法: - )

我认为如果你希望Person类在创建时有效,你必须将代码放在构造函数中。

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

答案 11 :(得分:1)

此解决方案基于munificents在构造函数中使用令牌的想法。完成此回答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);
        }
    }

答案 12 :(得分:1)

已经提到了具有不同权衡的多种方法。

  • 将工厂类嵌套在私有构造的类中只允许工厂构造1个类。此时,您最好使用Create方法和私人ctor。
  • 使用继承和受保护的ctor具有相同的问题。

我想将工厂建议为包含带有公共构造函数的私有嵌套类的分部类。您100%隐藏了工厂正在构建的对象,并且只通过一个或多个接口暴露您选择的对象。

我听到的用例就是当你想跟踪工厂中100%的实例时。这种设计保证了没有人,但工厂可以创建&#34;化学品&#34;在&#34;工厂&#34;中定义并且它不需要单独的组件来实现它。

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

答案 13 :(得分:0)

我不明白为什么要将“业务逻辑”与“业务对象”分开。这听起来像是一个面向对象的扭曲,你最终会采取这种方法将自己捆绑在一起。

答案 14 :(得分:0)

我不认为有一个解决方案并不比问题更糟糕,所有他上面都需要一个公共静态工厂,恕我直言是一个更糟糕的问题,不会阻止人们只是叫工厂使用你的对象 - 它不会隐藏任何东西。最好公开一个接口和/或将构造函数保持为内部,如果你能做到最好的保护,因为程序集是可信代码。

一个选项是有一个静态构造函数,它可以在某个地方注册一个类似IOC容器的工厂。

答案 15 :(得分:0)

这是另一种解决方案,“只因为你不能意味着你应该”......

它确实满足了将业务对象构造函数保持为私有并将工厂逻辑放在另一个类中的要求。之后会有点粗略。

工厂类有一个用于创建业务对象的静态方法。它派生自业务对象类,以便访问调用私有构造函数的静态受保护构造方法。

工厂是抽象的,所以你实际上不能创建它的实例(因为它也是一个业务对象,所以这很奇怪),它有一个私有构造函数,所以客户端代码不能从它派生

未阻止的是客户端代码派生自业务对象类并调用受保护(但未经验证)的静态构造方法。或者更糟糕的是,调用受保护的默认构造函数,我们必须添加以使工厂类首先进行编译。 (对于将工厂类与业务对象类分开的任何模式,偶然可能存在问题。)

我并不是想建议任何一个心智正常的人做这样的事情,但这是一个有趣的练习。 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()
    {
    }
}

答案 16 :(得分:0)

很高兴听到有关此解决方案的一些想法。 唯一能够创建“ MyClassPrivilegeKey”的是工厂。而“ MyClass”在构造函数中需要它。 这样就避免了对私人承包商/工厂“注册”的反思。

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