仅允许Factory方法实例化对象(防止实例化基类和未初始化的对象)

Kev*_*ice 9 .net c#

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

调用工厂方法为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();
  }
Run Code Online (Sandbox Code Playgroud)

结果:

Bob is cooking.
Alice is cleaning.
Sue is cooking.
Sue is cooking chicken.
Run Code Online (Sandbox Code Playgroud)

工作处理程序类:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

子级作业处理程序:

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

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

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

作业处理程序HandlerBase基类:

  1. 一个Dictionary<Job,Type>地图乔布斯处理程序类.
  2. 作业数据的私有设置器(即Person)阻止访问,除了工厂方法.
  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(); }
}
Run Code Online (Sandbox Code Playgroud)

看看工厂方法:

因为我已经在没有实例化的基础对象的情况下公开构造新对象,所以工厂方法首先将HandlerBase实例构造为用于调用所需派生类"复制构造函数"的"种子".

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

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

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

我不喜欢......

实现接口或类时,不会强制使用构造函数.派生类中的构造函数代码是必需的:

protected DerivedJobHandler(HandlerBase handler) : base(handler) { }
Run Code Online (Sandbox Code Playgroud)

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

也可以编写一个消除任何编译器错误的构造函数,而不是WORSE! - 导致运行时错误:

protected DerivedJobHandler() : base(null) { }
Run Code Online (Sandbox Code Playgroud)

我不喜欢在派生类实现中没有强制执行必需构造函数的方法.

jon*_*tur 2

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

  1. 处理程序登记
  2. 处理程序的构建
  3. 作业

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

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