【问题标题】:Only allow Factory method to instantiate objects (prevent instantiation of base class AND uninitialized objects)只允许工厂方法实例化对象(防止基类和未初始化对象的实例化)
【发布时间】:2011-07-03 03:17:48
【问题描述】:

我有一个用于处理“工作”的基类。工厂方法根据作业类型创建派生的“作业处理程序”对象,并确保使用所有作业信息初始化作业处理程序对象。

调用工厂方法为 Job 和 Person 分配请求处理程序:

public enum Job { Clean, Cook, CookChicken }; // List of jobs.

  static void Main(string[] args)
  {
    HandlerBase handler;
    handler = HandlerBase.CreateJobHandler(Job.Cook, "Bob");
    handler.DoJob();
    handler = HandlerBase.CreateJobHandler(Job.Clean, "Alice");
    handler.DoJob();
    handler = HandlerBase.CreateJobHandler(Job.CookChicken, "Sue");
    handler.DoJob();
  }

结果:

Bob is cooking.
Alice is cleaning.
Sue is cooking.
Sue is cooking chicken.

作业处理程序类:

public class CleanHandler : HandlerBase
{
  protected CleanHandler(HandlerBase handler) : base(handler) { }
  public override void DoJob()
  {
    Console.WriteLine("{0} is cleaning.", Person);
  }
}

public class CookHandler : HandlerBase
{
  protected CookHandler(HandlerBase handler) : base(handler) { }
  public override void DoJob()
  {
    Console.WriteLine("{0} is cooking.", Person);
  }
}

一个子类的 Job Handler:

public class CookChickenHandler : CookHandler
{
  protected CookChickenHandler(HandlerBase handler) : base(handler) { }
  public override void DoJob()
  {
    base.DoJob();
    Console.WriteLine("{0} is cooking chicken.", Person);
  }
}

最好的做事方式?我一直在努力解决这些问题:

  1. 确保所有派生对象都具有完全初始化的基础对象(已分配人员)。
  2. 防止实例化任何对象,而不是通过执行所有初始化的我的工厂方法。
  3. 防止基类对象的实例化。

作业处理程序HandlerBase 基类:

  1. Dictionary<Job,Type> 将作业映射到处理程序类。
  2. 作业数据(即 Person)的 PRIVATE setter 阻止访问,除非通过工厂方法。
  3. 没有默认构造函数和 PRIVATE 构造函数阻止构造,除非通过工厂方法。
  4. 受保护的“复制构造函数”是唯一的非私有构造函数。必须有实例化的 HandlerBase 才能创建新对象,而且只有基类工厂才能创建基 HandlerBase 对象。如果尝试从非基础对象创建新对象,“复制构造函数”会抛出异常(同样,除了工厂方法之外,阻止构造)。

看看基类:

public class HandlerBase
{
  // Dictionary maps Job to proper HandlerBase type.
  private static Dictionary<Job, Type> registeredHandlers =
    new Dictionary<Job, Type>() {
      { Job.Clean, typeof(CleanHandler) },
      { Job.Cook, typeof(CookHandler) },
      { Job.CookChicken, typeof(CookChickenHandler) }
    };

  // Person assigned to job. PRIVATE setter only accessible to factory method.
  public string Person { get; private set; }

  // PRIVATE constructor for data initialization only accessible to factory method.
  private HandlerBase(string name) { this.Person = name; }

  // Non-private "copy constructor" REQUIRES an initialized base object.
  // Only the factory method can make a HandlerBase object.
  protected HandlerBase(HandlerBase handler)
  {
    // Prevent creating new objects from non-base objects.
    if (handler.GetType() != typeof(HandlerBase))
      throw new ArgumentException("THAT'S ILLEGAL, PAL!");

    this.Person = handler.Person; // peform "copy"
  }

  // FACTORY METHOD.
  public static HandlerBase CreateJobHandler(Job job, string name)
  {
    // Look up job handler in dictionary.
    Type handlerType = registeredHandlers[job];

    // Create "seed" base object to enable calling derived constructor.
    HandlerBase seed = new HandlerBase(name);

    object[] args = new object[] { seed };
    BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;

    HandlerBase newInstance = (HandlerBase)Activator
      .CreateInstance(handlerType, flags, null, args, null);

    return newInstance;
  }

  public virtual void DoJob() { throw new NotImplementedException(); }
}

工厂方法概览:

因为在没有实例化基对象的情况下,我已经使新对象的公开构造成为不可能,所以工厂方法首先构造一个HandlerBase 实例作为调用所需派生类“复制构造函数”的“种子”。

工厂方法使用Activator.CreateInstance() 来实例化新对象。 Activator.CreateInstance() 查找与请求签名匹配的构造函数:

所需的构造函数是DerivedHandler(HandlerBase handler),因此,

  1. 我的“种子”HandlerBase 对象放在object[] args 中。
  2. 我将BindingFlags.InstanceBindingFlags.NonPublic 结合起来,以便CreateInstance() 搜索非公共构造函数(添加BindingFlags.Public 以查找公共构造函数)。
  3. Activator.CreateInstance() 实例化返回的新对象。

我不喜欢的...

在实现接口或类时不强制执行构造函数。派生类中的构造函数代码是强制性的:

protected DerivedJobHandler(HandlerBase handler) : base(handler) { }

然而,如果构造函数被遗漏,您不会得到一个友好的编译器错误,告诉您所需的确切方法签名:“'DerivedJobHandler' 不包含采用 0 个参数的构造函数”。

也可以编写一个构造函数来消除任何编译器错误,而不是——更糟糕!——导致运行时错误:

protected DerivedJobHandler() : base(null) { }

我不喜欢在派生类实现中没有强制要求构造函数的方法。

【问题讨论】:

  • 我一般不喜欢这个,但正如你所指出的,构造函数 不能 符合接口。对此的一种解决方案是只需要一个默认构造函数(可以在泛型中使用with T : new() 强制执行),然后是一个Init 方法或类似的“真正初始化”方法,这将成为接口的一部分。
  • @pst 在我的“生产”代码中,我选择了基类中的“设置”属性(不需要初始化,只需一个值结构)。派生对象不需要实现任何东西,因为设置是基础的一部分。不需要接口。我对泛型/默认构造函数的建议感到困惑(与下面的@Jason 建议相同)。我可以看到强制执行默认 new() 的价值,但这不需要消费代码知道派生类的类型吗?而不是HandlerBase,它必须指定类型HandlerBase&lt;Job1Handler&gt;???也许我还有东西要学……
  • @pst 我之前提出的问题是:stackoverflow.com/questions/6559662/…
  • 在默认的构造函数限制下,假设类型不是new Dictionary&lt;Job,Type&gt; {...},而是映射到一个函数:void addJobMapping&lt;T&gt;(T job, Type type) where T : Job, new()。这仍然不能确保它是一个有效的“类型”,但是可以在方法中强制执行,或者接受一个符合HandlerBase&lt;T&gt; 或类似的object。最后,C# 不是一门纯粹的语言,也不能描述类型系统中的所有内容。有时必须依赖非编译时强制执行的合同和单元测试。编码愉快。
  • void addJobMapping&lt;T&gt;(T job, BaseHandle&lt;T&gt; base) where T : Job, new() 可能是接受类型化处理程序的函数签名。但是,registeredHandlers 的签名将是Dictionary&lt;Job, BaseHandler&gt;,其中BaseHandler&lt;T&gt; : BaseHandler。请注意,这是确保编译时类型安全所必需的 函数(并假设它是添加新处理程序的唯一方法)。

标签: c# .net


【解决方案1】:

我在您的 HandlerBase 中看到了三个职责,如果它们相互分离,可能会简化设计问题。

  1. 处理程序注册
  2. 处理程序的构造
  3. 做作业

重组它的一种方法是将#1 和#2 放在工厂类上,将#3 放在具有内部构造函数的类上,这样只有工厂类才能根据您的内部要求调用它。您可以直接传入 Person 和 Job 值,而不是让构造函数从不同的 HandlerBase 实例中提取它们,这将使代码更易于理解。

一旦将这些职责分开,您就可以更轻松地独立发展它们。

【讨论】:

    【解决方案2】:

    每当您说“我想强制执行 x,但代码不会为我执行”时,通常答案是单元测试。

    然后,下一个进来制作处理程序的人将查看测试并知道他需要做什么才能符合要求。您甚至可以编写一个单元测试,循环遍历已注册处理程序的字典并构建每个处理程序。未能为新的构造函数创建正确的构造函数将导致测试失败。

    【讨论】:

      【解决方案3】:

      如果你想强制使用无参数构造函数,你可以这样做:

      class Base<T> where T : new() { } 
      
      class Derived : Base<Derived> {
          public Derived() { } // required!
      }
      

      除了无参数之外,您在编译时无需执行任何其他操作。

      【讨论】:

      • 我对你的帖子感到困惑。我实际上已经抑制了公共构造函数,以便只有基类工厂方法可以创建新对象。我也看不到使用泛型的情况,因为消费代码不必知道工厂返回的“作业处理程序”对象的类型(除了它是从 HandlerBase 派生的)。理论上,可以返回匿名类型。但是,我的解决方案要求派生类实现特定的构造函数——我发现无法强制执行此签名(例如实现接口时)很烦人。
      • @Kevin R:困惑?除了无参数构造函数之外,在强制特定构造函数的存在方面,您在编译时无能为力。我发现您的代码有些丑陋,并认为通过重新设计可以避免该问题。特别是,基类不应充当其派生类的工厂类。所以它代表处理程序的通用抽象并创建它们?不,这违反了 SRP。
      • 谢谢。批评是好的。我确实理解缺乏构造函数强制执行——你是否忽略了我的设计目标? (1) DISALLOW 默认构造函数(防止在工厂外创建)以确保仅由工厂初始化。 (2) 消除类型的外部知识(消费者请求作业处理程序,而不是类型)。与您的仿制药建议相反? SRP:必须在基础中实现工厂才能访问私有构造函数。弱 SRP 是静态工厂与实例基础成员。注意:`System.Net.WebRequest' 类也结合了基础和工厂。我的目标不好吗?为什么?再次,非常感谢!
      猜你喜欢
      • 1970-01-01
      • 2013-02-11
      • 2018-08-14
      • 1970-01-01
      • 2013-07-04
      • 2013-03-03
      • 1970-01-01
      • 2016-06-13
      • 1970-01-01
      相关资源
      最近更新 更多