4.6 实现单一职责原则
我们编写的所有对象和方法都应最多拥有一项职责。对象可以有多个方法,但是这些方法的组合都是为了实现其所在对象的单一目标。方法可以调用多个方法,每个方法都有各自不同的功能,但是该方法本身所执行的工作应当只有一种。
一个全知全能的方法称为“上帝方法”(God method);相应地,一个全知全能的对象则称为“上帝对象”(God object)。这种对象和方法是难以阅读、维护或排错的;并且通常它们的缺陷都会多次重复出现。编程基础扎实的程序员都会避免编写上帝对象和上帝方法。以下范例展示了职责多于一个的方法:
该方法的功能显然多于一个,因此它破坏了单一职责原则。接下来我们将其分解为若干功能单一的小方法;同时,由于该方法的参数多于两个,因此我们还将处理该方法中过多的参数。
在开始将其分解为功能单一的小方法之前,首先要梳理一下当前方法执行的动作。该方法首先将文本写入到文件中,此后创建电子邮件信息,向其中添加附件,最终发送电子邮件。因此,我们需要拥有以下功能的方法:
- 将文本写入文件
- 创建电子邮件信息
- 向电子邮件中添加附件
- 发送邮件
上述方法中和文件写入相关的参数有4个:文件夹、文件名称、文本内容以及媒体类型。其中文件夹和文件名可以组合为一个filename
参数。这是因为如果filename
和folder
在代码中是两个独立的参数,那么可以将其进行字符插值而合并为一个方法参数,例如$"{folder}{filename}"
。
至于媒体类型则可以在构造时在结构体内部进行私有设置。通过设置结构体中必要的属性,就可以用包含三个属性的结构体作为单一参数进行传递。以下是相应代码:
上述代码中,GetFileTimeStamp
方法的结果将附加到FileName
之后。如需保存文件则调用SaveTextFile()
方法。请注意,MimeType
是在内部设置为MimeType.TextPlain
的。虽然也可以简单地使用硬编码设置MimeType
,例如:MimeType = "text/plain";
。但是使用枚举不但有助于重用代码,而且无须记忆或上网查询特定MimeType
对应的文本。以下代码创建了enum
类并在enum
的值上添加了相应的描述:
创建enum
之后仍需找到一种方法获得枚举值上的描述信息,以便对变量进行赋值。因此我们需要创建一个扩展类来获得枚举值的描述信息。这样我们就可以设置MimeType
[1]:
上述代码若不使用扩展方法,则MimeType
属性的值为0
。若使用扩展方法,则扩展方法将返回MimeType
值的描述信息"text/plain"
。该扩展方法也可以依据需要在其他项目中使用。
接下来实现Smtp
类,它的职责是使用Smtp
协议发送邮件:
Smtp
类的构造器仅接收一个Credential
类型的参数。该参数用于登录邮件服务器。而邮件服务器是在构造器中指定的。调用SendMessage(MailMessage mailMessage)
方法就将发送邮件。
接下来我们编写DemoWorker
类,并将上述工作划分到不同的方法中:
DemoWorker
类比先前发送邮件的范例更整洁。其中,保存附件并将附件随邮件一起发送的主要方法是DoWork()
方法。该方法仅仅包含两行代码,第一行调用SaveTextFile()
方法而第二行则调用SendEmail()
方法。
SaveTextFile()
方法创建了TextFileData
结构体对象,设置了文件名和文本内容。随后调用TextFileData
结构体中的SaveTextFile()
方法。该方法负责将文本保存至指定的文件中。
SendEmail()
方法则创建了Smtp
对象。Smtp
类(的构造器)需要一个Credential
参数,而Credential
类(的构造器)有两个字符串类型的参数,一个为邮件地址,另一个为密码,它们均用于登录SMTP服务器。在SMTP服务器对象创建之后,即调用Send-Message(MailMessage mailMessage)
方法发送邮件。
SendMessage
方法需要提供MailMessage
对象。我们使用GetMailMessage()
方法创建MailMessage
对象并将其传入SendMessage(MailMessage mailMessage)
方法中。其中,GetMailMessage()
方法调用GetAttachment()
方法并将附件添加到MailMessage
对象中。
经过上述修改,代码变得更加紧凑易读了。而这就是高质量的代码易于修改和维护的关键原因:易读易懂。这也是要求方法短小整洁,并尽量少地使用参数的原因。
你的方法有没有破坏单一职责原则呢?如果有的话,那么应当考虑将方法分割为许多职责单一的方法。至此本章关于编写整洁函数的内容就介绍完了。接下来让我们总结并测试一下学到的知识。
[1]如需MimeType
枚举获得其描述,则需要编写一个扩展方法Description()
,并使用如下方式获得描述:attachment.ContentType.MediaType = MimeType.TextPlain.Description()
;不过原书中并没有给出Description
方法的实现。因此如需相关代码,请参考:https://github.com/PacktPublishing/Clean-Code-in-C-/blob/master/CH04/EnumExtensions.cs。——译者注