我提前道歉但这将是一个很长的问题。
我被困住了。我正在尝试学习单元测试,C#和设计模式 - 所有这些都是一次性的。 (也许那是我的问题。)因此我正在阅读单元测试艺术(Osherove),清洁代码(Martin)和Head First Design Patterns(O'Reilly)。
我现在才刚刚开始了解代表和事件(如果你是最近我的问题,你会看到)。我还是不太喜欢lambdas。
为了将所有这些情境化,我给自己开了一个学习项目,我称之为goAlarms。我有一个你想要的成员的Alarm类(NextAlarmTime,Name,AlarmGroup,Event Trigger等)
我希望闹钟的“定时器”是可扩展的,所以我创建了一个IAlarmScheduler接口,如下所示......
public interface AlarmScheduler
{
Dictionary<string,Alarm> AlarmList { get; }
void Startup();
void Shutdown();
void AddTrigger(string triggerName, string groupName, Alarm alarm);
void RemoveTrigger(string triggerName);
void PauseTrigger(string triggerName);
void ResumeTrigger(string triggerName);
void PauseTriggerGroup(string groupName);
void ResumeTriggerGroup(string groupName);
void SetSnoozeTrigger(string triggerName, int duration);
void SetNextOccurrence (string triggerName, DateTime nextOccurrence);
}
此IAlarmScheduler接口定义了一个组件,它将触发警报(Trigger),它将冒泡到我的Alarm类并引发警报本身的触发事件。它本质上是“Timer”组件。
我发现Quartz.net组件非常适合这个,所以我创建了一个实现IAlarmScheduler的QuartzAlarmScheduler类。
一切都很好。我的问题是Alarm类是抽象的,我想创建许多不同的警报。例如,我已经有一个心跳警报(触发每(int)间隔分钟),AppointmentAlarm(在设定日期和时间触发),每日警报(每天在X触发)以及其他可能。
Quartz.NET非常适合处理这个问题。
我的问题是设计问题。我希望能够在没有我的Alarm类(或任何派生类)知道任何有关Quartz的情况下实例化任何类型的警报。问题是Quartz有很棒的工厂,只返回我的Alarm类所需的触发器的正确设置。因此,例如,我可以通过使用TriggerUtils.MakeMinutelyTrigger为上述心跳警报创建触发器来获取Quartz触发器。或者TriggerUtils.MakeDailyTrigger用于每日警报。
我想我可以这样总结一下。间接或直接我希望我的警报类能够使用TriggerUtils.Make *类而不需要了解它们。我知道这是一个矛盾,但这就是我提出这个问题的原因。
我考虑过将一个委托字段放入警报中,该警报将分配给其中一个Make方法,但是通过这样做,我在警报和Quartz之间创建了一个硬依赖关系,我想避免出于单元测试目的和设计目的。我想在每个here使用QuartzAlarmScheduler中的类型的开关,但我知道这是一个糟糕的设计,我正在努力学习好的设计。
你们很棒,并提前感谢你们的答案。
赛斯
答案 0 :(得分:1)
也许你可以有一个AlarmFactory类来返回适当的IAlarmScheduler引用类型。它可能是一种隔离Quartz知识及其与不同子类关系的方法。
我不确定是否有可能使它成为IAlarmScheduler的子类完全不知道Quartz的细节,但完全有可能让它成为IAlarmScheduler的 clients 。这就是界面和工厂组合为您所做的。
答案 1 :(得分:0)
这是一个相当普遍的问题。您不控制框架基类,并且您希望编码到接口。使用HttpContext类在ASP.NET中发生同样的问题。如果您使用Google HttpContextWrapper ASP.NET MVC,您会发现很多有用的文章,讨论Microsoft如何破坏与System.Web.Abstractions命名空间的依赖关系。
以下是摘要。解决这个问题很麻烦,但这是一个很好理解的模式。
定义一个AlarmSchedulerWrapper,它接受IAlarmScheduler作为构造函数参数。 AlarmSchedulerWrapper将所有调用转发给组合的IAlarmScheduler。您现在可以传入实现IAlarmScheduler的QuartzAlarmScheduler。这是你唯一需要依赖的地方。
对于测试,您可以模拟AlarmScheduler或创建double(AlarmSchedulerDouble)。可以编写AlarmSchedulerDouble来模拟警报事件并记录它的调用(使其成为“间谍”)。
如果您要实现许多具体的AlarmScheduler实例,通常还会创建一个AlarmSchedulerBase。 AlarmSchedulerWrapper和AlarmSchedulerDouble会将它用作基类。
编辑:更详细地阅读您的问题,我发现这是您需要抽象的触发器。类似的答案。定义IAlarmTriggers,一个通过转发到Quartz来实现它们的包装器,以及一个用于测试的double。注意触发器实用程序可能是静态的。不过,只需从实例化的类中组合它们。
答案 2 :(得分:0)
我想我明白你想要什么,你的问题是什么,但如果我不知道,请告诉我。)
首先,暂时只是为了学习正确的OOD,忘记委托,事件和lambda。我说这个是有原因的,你以后会理解的。目前,重点关注接口及其实现。你所谓的“事件”也可以作为一个接口实现(例如IHaveAMethodThatYouShouldCall,方法是TheMethodToCall)。
现在,你的闹钟将实现接口IAmAnAlarmAndWhenAlarmSchedulerThinkItIsTheTimeHeWillLetMeKnow,只有方法是ItIsTheTime。
您的AlarmScheduler将使用唯一方法RegisterAlarm(IAmAnAlarmAndWhenAlarmSchedulerThinkItIsTheTimeHeWillLetMeKnow警报)实现接口IAmAlarmSchedulerThatWillNotifyAlarmWhenItIsTheTime。
此外,您的警报将在其构造函数中获取IAmAlarmSchedulerThatWillNotifyAlarmWhenItIsTheTime类型的对象,并调用其RegisterAlarm将其自己传递给它。此时传入的实际AlarmScheduler将在其私有变量中存储指向Alarm的指针,并在需要时调用其ItIsTheTime方法。
每当您创建Alarm时,您首先要创建AlarmScheduler并将其实例传递给Alarm的构造函数。
我希望这是有道理的。
答案 3 :(得分:0)
我同意罗布。 AlarmScheduler不是需要接口的东西。 ITrigger需要一个界面。
修改AlarmScheduler,使其方法适用于ITrigger接口。通过这种方式,AlarmScheduler可以使用ITrigger的实现,而无需知道它们是什么或它们是如何构建的。
从这里开始,您可以选择如何将ITriggers引入调度程序。
ITrigger CreateMyCustomTriggerType1(param1, param2, param3)
和
ITrigger CreateMyCustomTriggerType2(param1)
虽然我们正在研究这个主题 - 但使用Enums来决定类型是没有错的。使用枚举将指定某种类型的Switch语句来决定要创建的类型。从设计的角度来看,只要不重复switch语句就没有问题。如果您将F语句背后的Switch语句隔离开来,那么就完美的OOP设计而言,您已经完成了尽职调查。