【发布时间】: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);
}
}
最好的做事方式?我一直在努力解决这些问题:
- 确保所有派生对象都具有完全初始化的基础对象(已分配人员)。
- 防止实例化任何对象,而不是通过执行所有初始化的我的工厂方法。
- 防止基类对象的实例化。
作业处理程序HandlerBase 基类:
-
Dictionary<Job,Type>将作业映射到处理程序类。 - 作业数据(即 Person)的 PRIVATE setter 阻止访问,除非通过工厂方法。
- 没有默认构造函数和 PRIVATE 构造函数阻止构造,除非通过工厂方法。
- 受保护的“复制构造函数”是唯一的非私有构造函数。必须有实例化的 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),因此,
- 我的“种子”
HandlerBase对象放在object[] args中。 - 我将
BindingFlags.Instance和BindingFlags.NonPublic结合起来,以便CreateInstance() 搜索非公共构造函数(添加BindingFlags.Public以查找公共构造函数)。 - Activator.CreateInstance() 实例化返回的新对象。
我不喜欢的...
在实现接口或类时不强制执行构造函数。派生类中的构造函数代码是强制性的:
protected DerivedJobHandler(HandlerBase handler) : base(handler) { }
然而,如果构造函数被遗漏,您不会得到一个友好的编译器错误,告诉您所需的确切方法签名:“'DerivedJobHandler' 不包含采用 0 个参数的构造函数”。
也可以编写一个构造函数来消除任何编译器错误,而不是——更糟糕!——导致运行时错误:
protected DerivedJobHandler() : base(null) { }
我不喜欢在派生类实现中没有强制要求构造函数的方法。
【问题讨论】:
-
我一般不喜欢这个,但正如你所指出的,构造函数 不能 符合接口。对此的一种解决方案是只需要一个默认构造函数(可以在泛型中使用
with T : new()强制执行),然后是一个Init方法或类似的“真正初始化”方法,这将成为接口的一部分。 -
@pst 在我的“生产”代码中,我选择了基类中的“设置”属性(不需要初始化,只需一个值结构)。派生对象不需要实现任何东西,因为设置是基础的一部分。不需要接口。我对泛型/默认构造函数的建议感到困惑(与下面的@Jason 建议相同)。我可以看到强制执行默认 new() 的价值,但这不需要消费代码知道派生类的类型吗?而不是
HandlerBase,它必须指定类型HandlerBase<Job1Handler>???也许我还有东西要学…… -
@pst 我之前提出的问题是:stackoverflow.com/questions/6559662/…
-
在默认的构造函数限制下,假设类型不是
new Dictionary<Job,Type> {...},而是映射到一个函数:void addJobMapping<T>(T job, Type type) where T : Job, new()。这仍然不能确保它是一个有效的“类型”,但是可以在方法中强制执行,或者接受一个符合HandlerBase<T>或类似的object。最后,C# 不是一门纯粹的语言,也不能描述类型系统中的所有内容。有时必须依赖非编译时强制执行的合同和单元测试。编码愉快。 -
void addJobMapping<T>(T job, BaseHandle<T> base) where T : Job, new()可能是接受类型化处理程序的函数签名。但是,registeredHandlers的签名将是Dictionary<Job, BaseHandler>,其中BaseHandler<T> : BaseHandler。请注意,这是确保编译时类型安全所必需的 函数(并假设它是添加新处理程序的唯一方法)。