97

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

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

4

17 回答 17

69

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

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

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

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

于 2009-02-05T10:18:28.253 回答
56

看起来您只是想在创建对象之前运行一些业务逻辑 - 那么为什么不在“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);
于 2009-02-05T14:06:02.397 回答
55

或者,如果你想要真正花哨,反转控制:让类返回工厂,并使用可以创建类的委托来检测工厂。

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

:)

于 2009-02-05T10:37:52.550 回答
15

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

于 2009-02-05T10:17:19.783 回答
7

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

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

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

于 2009-02-05T10:29:57.107 回答
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();
    }
}

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

于 2018-05-19T05:15:03.307 回答
4

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

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

    private BusinessObject()
    {
    }
}
于 2009-02-05T10:31:47.977 回答
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;
    }
}

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

于 2009-02-05T11:10:41.690 回答
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.

于 2011-11-08T09:41:42.390 回答
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-04-23T00:32:35.267 回答
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();
  }
}
于 2009-02-05T13:08:46.567 回答
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);
        }
    }
于 2014-05-04T07:43:05.810 回答
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);
    }
}
于 2016-04-22T02:32:51.577 回答
0

我不明白您为什么要将“业务逻辑”与“业务对象”分开。这听起来像是对对象方向的扭曲,而你最终会因为采用这种方法而陷入困境。

于 2009-02-05T12:22:50.500 回答
0

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

一种选择是拥有一个静态构造函数,该构造函数在某处使用 IOC 容器之类的东西注册工厂。

于 2012-12-11T07:59:37.077 回答
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()
    {
    }
}
于 2014-07-09T09:02:04.843 回答
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);
            }
        }
    }
}
于 2018-10-23T13:50:29.520 回答