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


93

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

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


是MyBoClass方法不正确吗?
pradeeptp,2009年

2
我很困惑,为什么您不想让您的业务对象成为负责保护自己的不变量的对象?工厂模式的重点(据我所知)是要么A)处理具有许多依赖关系的复杂类的创建,要么B)让工厂决定返回什么运行时类型,只公开一个接口/抽象给客户讲课。将业务规则放在业务对象中是OOP的本质。
2015年

@kai是的,业务对象负责保护它的不变性,但是构造函数调用者必须在将参数传递给构造函数之前确保参数有效CreateBusinessObject方法的目的是在调用者不立即知道参数是否有效传递给构造函数时,在单个方法调用中验证参数AND(返回一个新的有效对象或返回一个错误)。
莱特曼2015年

2
我不同意。构造函数负责对给定参数进行任何必需的检查。如果我调用构造函数且未引发任何异常,则我相信创建的对象处于有效状态。从物理上讲,使用“错误”参数实例化类是不可能的。创建一个Distance带有负参数的实例应该抛出一个ArgumentOutOfRangeException例子,如果创建一个DistanceFactory执行相同检查的实例,您将一无所获。我仍然看不到你将在这里获得什么。
萨拉2015年

Answers:


55

看起来您只是想在创建对象之前运行一些业务逻辑-那么为什么不在“ 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;
    }
}

调用起来非常简单:

BusinessClass objBusiness = BusinessClass.CreateObject(someProperty);

6
啊,您也可以抛出异常而不是返回null。
里卡多·诺尔德

5
如果声明了自定义的构造函数,则没有私有的默认构造函数是没有意义的。同样,在这种情况下,使用静态的“构造函数”并不仅仅是在真实的构造函数中进行验证(因为它仍然是类的一部分),实际上并没有任何好处。甚至是WORSE,因为您现在可以获得空返回值,因此可以为NRE开放,“这个空意味着什么?” 问题。
萨拉2015年

通过接口/抽象基类要求这种体系结构的任何好方法?即,一个接口/抽象基类,指示实现者不应公开ctor。
Arash Motamedi

1
@Arash否。接口无法定义构造函数,并且定义了受保护的或内部构造函数的抽象类也不能阻止继承的类通过其自己的公共构造函数公开它。
里卡多·诺尔德

66

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

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

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

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


我考虑了一下,但是它将这些检查有效地移到了业务对象本身中,我试图避免这种情况。
用户

@ so-tester:您仍然可以在工厂中进行检查,而不是在BusinessObject类型中进行检查。
乔恩·斯基特

3
@JonSkeet我知道这个问题确实很老,但是我很好奇将CreateBusinessObject方法放在嵌套Factory类中而不是让静态方法直接作为BusinessObject类的方法有什么好处。这样做的动力?
Kiley Naro

3
@KileyNaro:嗯,这就是问题的所在:)不一定带来很多好处,但它回答了这个问题……虽然有时候这很有用-构建者模式浮现在脑海。(在那种情况下,构建器将是嵌套类,并且它将具有一个称为的实例方法Build。)
Jon Skeet

53

或者,如果您真的想花哨的话,请反转控制:让类返回工厂,并使用可以创建该类的委托对工厂进行检测。

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

:)


非常好的一段代码。对象创建逻辑在单独的类中,并且只能使用工厂创建对象。
Nikolai Samteladze 2013年

凉。有什么方法可以将BusinessObjectFactory的创建限制为静态BusinessObject.GetFactory?
Felix Keil 2015年

1
这种反演的优势是什么?
yatskovsky '16

1
@yatskovsky这只是确保对象只能以受控方式实例化的一种方法,同时仍然能够将创建逻辑分解为专用类。
Fabian Schmied

15

您可以将MyBusinessObjectClass类的构造函数设为内部,然后将其和工厂移至其自己的程序集中。现在,只有工厂应该能够构造该类的实例。


7

除了Jon的建议之外,您还可以首先将factory方法(包括检查)作为BusinessObject的静态方法。然后,将构造函数设为私有,其他所有人将被迫使用静态方法。

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

但真正的问题是-为什么您有此要求?将工厂或工厂方法移入类是否可以接受?


这几乎不是必需的。我只想将业务对象和逻辑完全分开。正如将这些检查放在页面的代码隐藏中是不合适的,我认为将这些检查放在对象本身中也是不合适的。好吧,也许是基本验证,但不是真正的业务规则。
用户

如果您只是为了方便查看而将类的逻辑和结构分开,则可以始终使用部分类,并且将二者都放在单独的文件中...
Grx70 2013年

7

这么多年后,这个问题被问到了,不幸的是,我看到的所有答案都在告诉您应该如何编写代码而不是给出直接答案。您正在寻找的实际答案是使用私有构造函数但使用公共实例化程序来创建类,这意味着您只能从其他现有实例中创建新实例...仅在工厂中可用:

您的课程的界面:

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

然后,如果您需要更多参数进行实例化或预处理您创建的实例,则可以轻松修改此代码。由于类构造函数是私有的,因此此代码将允许您通过工厂强制实例化。


4

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

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

    private BusinessObject()
    {
    }
}

2

因此,看起来我想做的事情无法以“纯粹”的方式完成。总是对逻辑类进行某种“回调”。

也许我可以用一种简单的方式做到这一点,只需在对象类中创建一个构造器方法,然后首先调用逻辑类来检查输入?

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

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


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()初始化程序。但是唯一需要参考的是商业工厂本身。

我们得到了我们想要的东西:唯一可以创造的人BusinessObjectBusinessFactory


因此,您确定对象的唯一可用的公共构造函数需要一个Factory,并且只能通过调用Factory的静态方法来实例化所需的Factory参数吗?似乎是一个可行的解决方案,但是我觉得,public static IBusinessObject New(BusinessFactory factory) 如果有人维护代码,类似这样的代码片段会引起很多人的注意。
2014年

@Flater同意。我将参数名称更改factoryasFriend。在我的代码库中,它将显示为public static IBusinessObject New(BusinessFactory asFriend)
Reuven Bass,

我一点都不明白。它是如何工作的?工厂无法创建IBusinessObject的新实例,也没有这样做的意图(方法)...?
lapsus 2015年

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

请尝试我的方法,“工厂”是处理程序的容器,只有它可以自己创建一个实例。经过一些修改,它可能会满足您的需求。
林2015年

1
在您的示例中,以下操作不可能(没有意义)吗?:factory.DoWork();
Whyser

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

1

该解决方案基于在构造函数中使用令牌的丰富想法。完成此答案后,请确保对象仅由工厂(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);
        }
    }

1

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

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

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

我听说过的用例是当您想在工厂中跟踪100%的实例时。这种设计不能保证没有人,但是工厂可以访问在“工厂”中定义的“化学物质”实例的创建,并且消除了为实现该目的而需要单独组装的需求。

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

我不明白为什么要将“业务逻辑”与“业务对象”分开。这听起来像是面向对象的扭曲,并且您最终会采用这种方法将自己束缚在结中。


我也不明白这种区别。有人可以解释为什么这样做,以及业务对象中将包含什么代码吗?
pipTheGeek

这只是可以使用的一种方法。我希望业务对象主要是数据结构,但不要将所有字段都打开以进行读/写。但是真正的问题是,只能借助工厂方法而不是直接实例化对象。
用户

@吉姆:我个人同意你的观点,但是从专业上讲,这是不断进行的。我当前的项目是一个Web服务,它需要一个HTML字符串,根据一些预设规则对其进行清理,然后返回清理后的字符串。我需要遍历自己的代码的7层“因为我们所有的项目都必须从相同的项目模板构建”。理所当然的是,大多数人都在SO上问这些问题,没有获得改变整个代码流程的自由。
2014年

0

我认为没有比这个问题更糟的解决方案了,他上面的一切都需要一个公共静态工厂,恕我直言,这是一个更糟糕的问题,不会阻止人们仅仅打电话给工厂来使用您的对象-它不会隐藏任何东西。最好公开一个接口和/或将构造函数保留在内部,如果可以的话,这是最好的保护,因为程序集是受信任的代码。

一种选择是拥有一个静态构造函数,该构造函数在一个带有IOC容器之类的地方注册工厂。


0

这是“仅仅因为您不能代表您应该这样做”的另一种解决方案……

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

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

工厂是抽象的,因此您实际上不能创建它的实例(因为它也是一个业务对象,因此很奇怪),并且它具有私有构造函数,因此客户端代码无法从中派生。

不能阻止的是,客户端代码源自业务对象类并调用受保护(但未经验证)的静态构造方法。或更糟糕的是,调用必须添加的受保护的默认构造函数以使工厂类首先进行编译。(顺便说一下,任何将工厂类与业务对象类分开的模式都可能是一个问题。)

我并不是要建议任何有头脑的人都应该做这样的事情,但这是一个有趣的练习。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()
    {
    }
}

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);
            }
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.