Tutorialsteacher

关注我们

文章
  • C#
  • C# 面向对象编程
  • ASP.NET Core
  • ASP.NET MVC
  • LINQ
  • 控制反转 (IoC)
  • Web API
  • JavaScript
  • TypeScript
  • jQuery
  • Angular 11
  • Node.js
  • D3.js
  • Sass
  • Python
  • Go lang
  • HTTPS (SSL)
  • 正则表达式
  • SQL
  • SQL Server
  • PostgreSQL
  • MongoDB
  • C# - 入门
  • C# - 版本历史
  • C# - 第一个程序
  • C# - 关键词
  • C# - 类和对象
  • C# - 命名空间
  • C# - 变量
  • C# - 隐式类型变量
  • C# - 数据类型
  • 数字
  • 字符串
  • DateTime
  • 结构体
  • 枚举
  • StringBuilder
  • 匿名类型
  • 动态类型
  • 可空类型
  • C# - 值类型和引用类型
  • C# - 接口
  • C# - 运算符
  • C# - if else 语句
  • C# - 三元运算符 ?
  • C# - Switch 语句
  • C# - For 循环
  • C# - While 循环
  • C# - Do-while 循环
  • C# - 分部类
  • C# - Static 关键字
  • C# - 数组
  • 多维数组
  • 交错数组
  • C# - 索引器
  • C# - 泛型
  • 泛型约束
  • C# - 集合
  • ArrayList
  • List
  • SortedList
  • Dictionary
  • Hashtable
  • Stack
  • Queue
  • C# - 元组
  • C# - 值元组
  • C# - 内置异常
  • 异常处理
  • throw 关键字
  • 自定义异常
  • C# - 委托
  • Func 委托
  • Action 委托
  • Predicate 委托
  • 匿名方法
  • C# - 事件
  • C# - 协变
  • C# - 扩展方法
  • C# - 流 I/O
  • C# - File 类
  • C# - FileInfo 类
  • C# - 对象初始化器
  • OOP - 概述
  • 面向对象编程
  • 抽象
  • 封装
  • 关联与组合
  • 继承
  • 多态
  • 方法重写
  • 方法隐藏
  • C# - SOLID 原则
  • 单一职责原则
  • 开闭原则
  • 里氏替换原则
  • 接口隔离原则
  • 依赖倒置原则
  • 设计模式
  • 单例模式
  • 抽象工厂模式
  • 工厂方法模式
Entity Framework Extensions - 提升 EF Core 9
  批量插入
  批量删除
  批量更新
  批量合并

SOLID:开闭原则

开闭原则 (OCP) 是 SOLID 的第二个原则。Bertrand Meyer 博士在他的《面向对象软件构造》一书中提出了这个术语。

开闭原则指出:

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

那么,扩展和修改是什么意思呢?

这里的扩展意味着在不修改系统的情况下向系统添加新功能。插件系统是 OCP 的主要示例,其中通过新功能添加新功能,而无需修改现有功能。

OCP 表示类方法的行为应该在不修改其源代码的情况下进行更改。您不应该编辑方法的代码(修复错误是可以的),而应该使用多态性或其他技术来改变它的作用。通过编写新代码来添加新功能。

在 C# 中,可以使用以下方法应用开闭原则:

  1. 使用函数参数
  2. 使用扩展方法
  3. 使用类、抽象类或基于接口的继承
  4. 泛型
  5. 组合和依赖注入

为了演示 OCP,我们以下面的 `Logger` 类为例。假设您是此类的创建者,其他程序员希望重用您的类,这样他们就不必花费时间重写它(SOLID 原则提倡可重用性)。

示例:Logger 类
public class Logger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }

    public void Info(string message)
    {
        Console.WriteLine($"Info: {message}");
    }

    public void Debug(string message)
    {
        Console.WriteLine($"Debug: {message}");
    }
}

现在,一些开发人员希望更改调试消息以适应他们的需求。例如,他们希望调试消息以 `"Dev Debug ->"` 开头。因此,为了满足他们的需求,您需要编辑 `Logger` 类的代码,并为他们创建一个新方法或修改现有的 `Debug()` 方法。如果您更改现有的 `Debug()` 方法,那么其他不希望此更改的开发人员也会受到影响。

使用 OCP 解决此问题的一种方法是使用基于类的继承(多态)和重写方法。您可以将 `Logger` 类的所有方法标记为 `virtual`,这样如果有人想更改任何方法,他们就可以将 `Logger` 类继承到一个新类中并重写它。

示例:虚方法
public class Logger
{
    public virtual void Log(string message)
    {
        Console.WriteLine(message);
    }

    public virtual void Info(string message)
    {
        Console.WriteLine($"Info: {message}");
    }

    public virtual void Debug(string message)
    {
        Console.WriteLine($"Debug: {message}");
    }
}

现在,一个新类可以继承 `Logger` 类并更改一个或多个方法的行为。希望更改调试消息的开发人员将创建一个新类,继承 `Logger` 类并重写 `Debug()` 方法以显示他们想要的消息,如下所示。

示例:通过重写方法修改类
public class NewLogger : Logger
{
    public override void Debug(string message)
    {
        Console.WriteLine($"Dev Debug -> {message}");
    }
}

他们现在将使用上面的类来显示他们想要的调试消息,而无需编辑原始类的源代码。

示例
public class Program
{
    public static void Main(string[] args)
    {
        Logger logger = new Logger();
        logger.Debug("Testing debug");

        Logger newlogger = new NewLogger();
        newlogger.Debug("Testing debug ");
    }
}
尝试一下
输出
Debug: Testing debug Dev Debug -> Testing debug

因此,使用继承的 OCP 使其“对扩展开放,对修改关闭”。

我们再举一个例子。以下是我们之前在 SRP 部分中创建的 `Course` 类。

示例
public class Course
{
    public int CourseId { get; set; }
    public string Title { get; set; }
    public string Type { get; set; }

    public void Subscribe(Student std)
    {
        Logger.Log("Starting Subscribe()");

        //apply business rules based on the course type live, online, offline, if any 
        if (this.Type == "online")
        {
            //subscribe to online course 
        }
        else if (this.Type == "offline")
        {
            //subscribe to offline course 
        }

        // payment processing
        PaymentManager.ProcessPayment();

        //create CourseRepository class to save student and course into StudentCourse table  

        // send confirmation email
        EmailManager.SendEmail();

        Logger.Log("End Subscribe()");
    }
}

每当需要添加新类型的课程时,我们都必须编辑上面的 `Course` 类。我们将不得不添加一个 if 条件或 switch 案例来处理课程类型。此外,上面的 `Course` 类不遵循单一职责原则,因为如果订阅课程的处理方式发生任何变化或需要添加新类型的课程,那么我们将不得不更改 `Course` 类。

要将 OCP 应用于我们的 `Course` 类,基于抽象类的继承更合适。我们可以创建一个抽象类作为基类,然后为每种课程类型创建一个新类,并在每个类中实现 `Subscribe()` 方法,该方法将执行所有必要的订阅步骤,如下所示。

示例:遵循 OCP 的类
public abstract class Course
{
    public int CourseId { get; set; }
    public string Title { get; set; }

    public abstract void Subscribe(Student std);
}
 
public class OnlineCourse : Course
{
    public override void Subscribe(Student std)
    {
        //write code to subscribe to an online course
    }
}

public class OfflineCourse : Course
{
    public override void Subscribe(Student std)
    {
        //write code to subscribe to a offline course
    }
}

如您所见,`Course` 类现在是一个抽象类,其中 `Subscribe()` 方法是一个抽象方法,需要在继承 `Course` 类的类中实现。这样,不同课程类型有单独的 `Subscribe()` 函数(关注点分离)。您可以在将来为新类型的课程创建一个新类,该类继承 `Course` 类。这样,您就不必编辑现有类了。

您现在可以按照如下所示将学生订阅到课程:

public class Program
{
    public static void Main(string[] args)
    {
        Student std = new Student() { FirstName = "Steve", LastName = "Jobs" };
        
        Course onlineSoftwareEngCourse = new OnlineCourse() { Title = "Software Engneering" };

        onlineSoftwareEngCourse.Subscribe(std);
    }
}

OCP 的优点

  1. 通过不修改现有类来最大限度地减少错误的可能性。
  2. 通过添加新类轻松添加新功能,其中当前功能不依赖于新类。
  3. 促进单一职责原则
  4. 对每个类进行单元测试
延伸阅读
  • CleanCoder:开闭原则
  • 为什么开闭原则是你需要知道但却不知道的
TUTORIALSTEACHER.COM

TutorialsTeacher.com 是您权威的技术教程来源,旨在通过循序渐进的方法,指导您掌握各种网络和其他技术。

我们的内容旨在帮助所有水平的学习者轻松快速地学习技术。访问此平台即表示您已阅读并同意遵守我们的使用条款和隐私政策,这些条款和政策旨在保护您的体验和隐私权。

[email protected]

关于我们使用条款隐私政策
copywrite-symbol

2024 TutorialsTeacher.com. (v 1.2) 版权所有。