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

时间:2011-07-03 03:17:48

标签: c# .net

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

调用工厂方法为作业和人员分配请求处理程序:

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

子级别的工作处理程序:

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. 防止实例化基类对象。
  4. 作业处理程序HandlerBase基类:

    1. Dictionary<Job,Type>将作业映射到处理程序类。
    2. 作业数据的私人设定者(即人员)会阻止除工厂方法以外的访问。
    3. 除了工厂方法之外,没有默认构造函数和PRIVATE构造函数阻止构造。
    4. 受保护的“复制构造函数”是唯一的非私有构造函数。必须有一个实例化的HandlerBase来创建一个新对象,只有基类工厂可以创建一个基本的HandlerBase对象。如果尝试从非基础对象创建新对象,则“复制构造函数”会抛出异常(同样,除了工厂方法之外,还会阻止构造)。
    5. 查看基类:

      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()实例化返回的新对象。
      4. 我不喜欢......

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

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

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

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

        protected DerivedJobHandler() : base(null) { }
        

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

3 个答案:

答案 0 :(得分:1)

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

然后,下一个进入处理程序的人将查看测试并知道他需要做些什么才能符合。您甚至可以编写一个单元测试,该测试循环遍历注册处理程序的字典并构建每个字典。如果没有为新的构造函数创建正确的构造函数,将无法通过该测试。

答案 1 :(得分:1)

我在你的HandlerBase中看到三个责任,如果彼此分离,可能会简化设计问题。

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

    一旦这些责任分开,您就可以更容易地独立发展它们。

答案 2 :(得分:0)

如果要强制执行无参数构造函数,可以执行以下操作:

class Base<T> where T : new() { } 

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

除了无参数之外,你在编译时没有别的事情。