这是SOLID原则这一系列的第三篇文章,主要来描述开闭(OCP)原则。OCP指出“所有的类或者类似的源代码应该对拓展开放,对修改关闭”。“对拓展开放”意思是你应该设计你的类,以便于有新需求的时候新增新的功能。“对修改关闭”意思是一旦你开发完一个类就不能再修改, 除非是修复bug。
原则的两个部分似乎是矛盾的。可是如果你正确的组织你的类和依赖关系,你无需修改现存代码就可以新增功能。通常你达到这个目的要通过参考抽象依赖关系,比如接口或抽象类而不是使用具体类。 这样的接口一旦开发完就被固定,所以依赖于它们的类可以依赖于不变的抽象。通过创建新的类实现接口添加新功能。应用OCP到你的项目中,一旦源码经过编写、测试以及调试,将被限制修改。这降低了现存代码引入新bug的风险,使得软件更加健壮。使用接口进行依赖的另外一个作用是减少了耦合性,并且增加了灵活性。
示例代码
为了演示OCP的应用,我们可以考虑一些违反它的C#代码,并解释如何重构类遵循以下原则:
public class Logger
{
public void Log(string message, LogType logType)
{
switch (logType)
{
case LogType.Console:
Console.WriteLine(message);
break;
case LogType.File:
// Code to send message to printer
break;
}
}
}
public enum LogType
{
Console,
File
}
上述示例代码是用于记录消息的基本模块。 Logger类具有接受要记录的消息和要执行的日志记录类型的单一方法。 switch语句根据程序是否将消息输出到控制台或默认打印机来更改操作。
重构代码
我们可以轻松重构logging代码,以实现符合OCP原则。首先我们需要移除LogType枚举。因为这会限制可以包含的Logging类型。而不是传递type到logger,我们将为我们需要的每一种消息logger创建一个新类。在最后的代码中,我们将有两个这样的类,命名为“ConsoleLogger”和“PrinterLogger”。之后可以添加其他的logger类型,而无需修改现存的代码。Logger类仍然执行所有日志记录,但使用上述消息记录器类之一来输出消息。为了减少类的耦合性,每个消息类都实现IMessageLogger接口。Logger类从来不知道使用的日志记录的类型,因为它的依赖关系使用构造函数注入作为IMessageLogger实例提供。
重构代码如下:
public class Logger
{
IMessageLogger _messageLogger;
public Logger(IMessageLogger messageLogger)
{
_messageLogger = messageLogger;
}
public void Log(string message)
{
_messageLogger.Log(message);
}
}
public interface IMessageLogger
{
void Log(string message);
}
public class ConsoleLogger : IMessageLogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class PrinterLogger : IMessageLogger
{
public void Log(string message)
{
// Code to send message to printer
}
}
网友评论