C# 是否有一个“问题”;对",;如何抽象出我的代码?
我已经在C#中开发了大约12个月了(从零开始,除了一点PHP脚本攻击之外,没有任何开发经验),我喜欢认为我的技能已经发展到可以编写应用程序并完美执行其功能的水平 但是,我仍然对最佳编码实践感到有点困惑,我知道这段代码很糟糕:C# 是否有一个“问题”;对",;如何抽象出我的代码?,c#,oop,C#,Oop,我已经在C#中开发了大约12个月了(从零开始,除了一点PHP脚本攻击之外,没有任何开发经验),我喜欢认为我的技能已经发展到可以编写应用程序并完美执行其功能的水平 但是,我仍然对最佳编码实践感到有点困惑,我知道这段代码很糟糕: class Example1 { public static Alert GenerateAlert() { Alert AlertObject = new Alert(); AlertObject.AlertDatetim
class Example1
{
public static Alert GenerateAlert()
{
Alert AlertObject = new Alert();
AlertObject.AlertDatetime = DateTime.Now;
AlertObject.AlertHasRecords = false;
return AlertObject;
}
}
例如,如果AlertDatetime
需要的不仅仅是像DateTime.Now这样的简单行代码>我最终会扩展出一个庞大的函数。不好
然而,我看不出以下两个例子有什么问题(我赞成例子2)
一个例子比另一个好吗?如果是,为什么?或者有完全不同的方法吗?你的第一个例子很好。
如果以后,AlertDateTime
需要初始化一个更复杂的函数,您可以随时将代码重构为类似示例3的内容。在那之前,尊重亲吻(保持简单)和原则
注意,示例1和3之间的接口(公开可用的方法及其签名)没有改变。这是一件好事。这意味着您可以在这些样式之间移动,而无需修改使用类的代码
然而,示例2有很多问题:
- 基本上是说,你不应该在没有充分理由的情况下公开某些东西。为什么要将新生成的警报存储在可公开访问的“全局变量”中
- 示例2的行为有所不同:如果调用两次
GenerateAlert
,则两次都将返回对同一警报对象的引用。(想想如果你今天打一次电话,明天再打一次电话会发生什么。)
作为旁注,可以改进示例3中方法的命名。试着孤立地考虑每个方法:PopulateAlertDate()
不填充警报日期。它返回可用于填充警报日期的日期。名称GetDefaultAlertDate()
可能更合适。如果这些函数中的逻辑不仅仅是赋值true或false,那么您可能需要使用工厂和接口。遵循坚实原则的完全抽象的代码如下所示:
public class AlertFactory : IAlertFactory {
IAlertDatePopulator alertDatePopulator;
IAlertRecordsChecker alertRecordsChecker;
public AlertFactory(IAlertDatePopulator alertDatePopulator, IAlertRecordsChecker alertRecordsChecker) {
this.alertDatePopulator= alertDatePopulator;
this.alertRecordsChecker = alertRecordsChecker;
}
public Alert GenerateAlert() {
Alert alertObject = new Alert();
alertObject.AlertDatetime = alertDatePopulator.Populate();
alertObject.AlertHasRecords = alertRecordsChecker.Check();
return alertObject;
}
}
与
然后,您可以为这些接口添加具体实现,例如:
public class DateTimeNowAlertDatePopulator : IAlertDatePopulator {
public DateTime Populate() { return DateTime.Now; }
}
public class SomeCalculationAlertDatePopulator : IAlertDatePopulator {
public DateTime Populate() { return /* something calculated */; }
}
分别
然后,您可以创建已配置的工厂:
public class DateNowAndRecordsFalseAlertFactory : AlertFactory {
public DateNowAndRecordsFalseAlertFactory ()
: base (new DateTimeNowAlertDatePopulator(), new AlwaysFalseAlertRecordsChecker()) { }
}
public class DateNowAndCalculatedRecordsAlertFactory : AlertFactory {
public DateNowAndCalculatedRecordsAlertFactory ()
: base (new SomeCalculationAlertDatePopulator(), new AlwaysFalseAlertRecordsChecker()) { }
}
然后使用您的工厂:
var alertFactory = new DateNowAndRecordsFalseAlertFactory ();
var myAlert1 = alertFactory.GenerateAlert();
var alertFactory2 = new DateNowAndCalculatedRecordsAlertFactory();
var myAlert2 = alertFactory2.GenerateAlert();
对于一个简单的功能来说,这似乎有很多代码,但是如果您希望有很多扩展,并且会出现很多逻辑,那么这就是遵循开/关原则的干净代码(对扩展开放(通过添加新的接口实现),但对修改关闭(不再需要修改现有代码))
与依赖项注入一起使用时最有效。然后将工厂配置为:
public class DateNowAndRecordsFalseAlertFactory : AlertFactory {
public DateNowAndRecordsFalseAlertFactory (DateTimeNowAlertDatePopulator alertDatePopulator, AlwaysFalseAlertRecordsChecker alertRecordsChecker)
: base (alertDatePopulator, alertRecordsChecker) { }
}
只要做:
var alertFactory = someDiContainer.Resolve<DateNowAndRecordsFalseAlertFactory>();
var-alertFactory=someDiContainer.Resolve();
+1为海因茨的伟大答案
我要补充的是,在示例3中,您使用的是立面模式的变体。您正在使用复杂且重复的初始化逻辑包装一个类,并且还隐藏了该对象的接口并公开了新方法。如果以后有几种不同的方法来创建同一个对象,则应该考虑<强>工厂模式< /强> ./p>
注意:如果没有理由一次使用另一个变体,您应该首先倾向于将一些代码放在原始类的构造函数中
示例2类似于单例反模式,它的另一个用途是保留类的一个实例。这通常适用于您希望一次性创建的服务。即使如此,您最好查看依赖项容器,以获得更强大的单元测试功能。您正在尝试实例化一个对象,而我看不出使用静态方法有什么意义(factory已经有了答案,您真的需要吗?)
在您必须创建此对象的地方,只需执行以下操作
var alert = new Alert();
如果要在创建对象后使用默认值自定义某些属性,请使用以下快捷方式
var anotherAlert = new Alert() { AlertDatetime = DateTime.Now };
通常,您应该以最多可用的方式创建对象的实例,因此,如果您总是必须使用当前日期来构造它,则构造函数通常会这样做:
public class Alert
{
// do not add class name to property
public DateTime DateTime {get; set;}
// this don't need initialization if default value is false
public bool HasRecords {get; set;}
public Alert()
{
DateTime = DateTime.Now;
}
}
Example3
是一条路要走。不能少,不能多。Example2
在许多方面都不好,除非您需要单例。@tornup类似地,CheckForAlertRecords
不会检查警报是否有任何记录(您将返回alert.AlertHasRecords
,以表明这一点,假设添加记录的过程将此字段设置为true
)。在KISS模式下,我现在坚持使用示例1;如果要进行重构,示例2中的函数(将字段设置为false
)可能是实现重构的方法,但调用类似于ResetAlertRecords
(并且可能/应该删除现有对象已有的任何记录)。
var alert = new Alert();
var anotherAlert = new Alert() { AlertDatetime = DateTime.Now };
public class Alert
{
// do not add class name to property
public DateTime DateTime {get; set;}
// this don't need initialization if default value is false
public bool HasRecords {get; set;}
public Alert()
{
DateTime = DateTime.Now;
}
}