C#面向对象编程中开闭原则的示例详解

目录
  • 开闭原则
  • C# 示例
    • 改进
  • 总结

在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集,在他2000年的论文《设计原则与设计模式》中首次提出。

SOLID 原则包含:

  • S:单一功能原则(single-responsibility principle)
  • O:开闭原则(open-closed principle)
  • L:里氏替换原则(Liskov substitution principle)
  • I:接口隔离原则(Interface segregation principle)
  • D:依赖反转原则(Dependency inversion principle)

本文我们来介绍开闭原则。

开闭原则

在面向对象编程领域中,开闭原则 (open-closed principle, OCP) 规定软件中的对象(类,模块,函数等等)应该对于扩展是开放的,而对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用品质的过程。遵循开闭原则的代码在扩展时并不发生改变,因此无需这些过程。

具体到类,也就是说,在不修改类本身代码的情况下,应该是可以扩展它的行为的。

C# 示例

让我们回顾一下上一篇文章单一功能原则中提到的 AreaCalculator 类,

class AreaCalculator
{
    private List<object> _shapes;

    public AreaCalculator(List<object> shapes)
    {
        _shapes = shapes;
    }

    /// <summary>
    /// 计算所有形状的面积总和
    /// </summary>
    /// <returns></returns>
    public double Sum()
    {
        List<double> areas = new List<double>();

        foreach (var item in _shapes)
        {
            if (item is Square s)
            {
                areas.Add(Math.Pow(s.SideLength, 2));
            }
            else if (item is Circle c)
            {
                areas.Add(Math.PI * Math.Pow(c.Radius, 2));
            }
        }

        return areas.Sum();
    }
}

对于上面的计算方法,考虑这样一种场景,用户想要计算一些其它形状的面积总和,比如三角形、矩形、五边形等等…… 您将不得不反复编辑此类以添加更多的 if/else 块,这就违反了开闭原则。

改进

一个更好的做法是,将计算每个形状的面积的逻辑从 AreaCalculator 类中移除,并将其添加到对应每个形状的类中。我们可以定义一个带有 CalcArea 方法的接口 IShape,然后让每个形状都实现这个接口。

接口 IShape:

interface IShape
{
    /// <summary>
    /// 计算面积
    /// </summary>
    /// <returns></returns>
    double CalcArea();
}

修改后的 Square 和 Circle 类:

/// <summary>
/// 正方形
/// </summary>
class Square : IShape
{
    public Square(double length)
    {
        SideLength = length;
    }
    public double SideLength { get; init; }

    public double CalcArea()
    {
        return Math.Pow(SideLength, 2);
    }
}

/// <summary>
/// 圆形
/// </summary>
class Circle : IShape
{
    public Circle(double radius)
    {
        Radius = radius;
    }

    public double Radius { get; init; }

    public double CalcArea()
    {
        return Math.PI * Math.Pow(Radius, 2);
    }
}

AreaCalculator 类也要对应做一些修改:

class AreaCalculator
{
    private List<IShape> _shapes;

    public AreaCalculator(List<IShape> shapes)
    {
        _shapes = shapes;
    }

    /// <summary>
    /// 计算面积总和
    /// </summary>
    /// <returns></returns>
    public double Sum()
    {
        List<double> areas = new List<double>();

        foreach (var item in _shapes)
        {
            areas.Add(item.CalcArea());
        }

        return areas.Sum();
    }
}

此时,如果我们有一个新的形状需要进行计算,我们可以直接添加一个实现了接口 IShape 的新类,而无需修改 AreaCalculator 类的代码,比如添加一个长方形类:

/// <summary>
/// 长方形
/// </summary>
class Rectangle : IShape
{
    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public double Width { get; init; }
    public double Height { get; init; }

    public double CalcArea()
    {
        return Width * Height;
    }
}

处理输出格式的 SumCalculatorOutputter 类同样无需修改:

class SumCalculatorOutputter
{
    protected AreaCalculator _calculator;

    public SumCalculatorOutputter(AreaCalculator calculator)
    {
        _calculator = calculator;
    }

    public string String()
    {
        return $"Sum of the areas of provided shapes: {_calculator.Sum()}";
    }

    public string JSON()
    {
        var data = new { Sum = _calculator.Sum() };
        return System.Text.Json.JsonSerializer.Serialize(data);
    }
}

然后,我们修改 Main 方法中的代码来测试一下:

static void Main(string[] args)
{
    var shapes = new List<IShape> {
            new Circle(2),
            new Square(5),
            new Rectangle(2,3)
    };

    var areaCalculator = new AreaCalculator(shapes);
    var outputer = new SumCalculatorOutputter(areaCalculator);
    Console.WriteLine(outputer.JSON());
    Console.WriteLine(outputer.String());
}

运行一下,输出结果为:

{"Sum":43.56637061435917}
Sum of the areas of provided shapes: 43.56637061435917

现在,这些类的设计,既遵循了单一功能原则,又遵循了开闭原则。

总结

本文我介绍了 SOLID 原则中的开闭原则 (open-closed principle),并通过 C# 代码示例简明地诠释了它的含意和实现,希望对您有所帮助。

参考文档:

https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design

到此这篇关于C#面向对象编程中开闭原则的示例详解的文章就介绍到这了,更多相关C#开闭原则内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#面向对象设计原则之里氏替换原则

    里氏替换原则(LSP) 定义:在任何父类出现的地方都可以用它的子类类替换,且不影响功能.解释说明:其实LSP是对开闭原则的一个扩展,在OO思想中,我们知道对象是由一系列的状态和行为组成的,里氏替换原则说的就是在一个继承体系中,对象应该具有共同的外在特性,使用LSP时,如果想让我们的程序达到一个父类出现的地方都可以用它的子类来替换且不影响功能,那么这个父类也应该尽量声明出子类所需要的一些公共的方法,父类被子类替换之后,会比较顺利,那么为什么说它是对开闭原则的一个扩展呢?因为我们在开闭原则中说尽量使

  • C#面向对象设计原则之接口隔离原则

    接口隔离原则(ISP) 定义:使用多个专门的接口比使用单一的总接口要好.即不要把鸡蛋都放到一个篮子里.好处:比较灵活.方便,不想实现的或不用实现的可以不实现.解释说明:大部分人都喜欢用一个接口把需要用到的方法全部声明出来,但是ISP建议我们使用多个专门的接口比使用单一的总接口要好,也就是一个接口里的方法多的话,实现起来不是很方便. 示例1: using System; using System.Collections.Generic; using System.Linq; using Syste

  • C#面向对象设计原则之单一职责原则

    单一职责原则(SRP) 定义:系统中的每一个类都应该只有一个职责. 好处:高内聚.低耦合. 解释说明: 单一职责也就是说我们应该让一个类或一个对象只做一件事情,每个类所要关注的就是自己要完成的职责是什么,能够引起这个类变化的原因也应该只有一个,这也是后面提到的所有的设计模式都会遵守的一个原则. 高内聚:先按照面向对象的封装特性来理解,封装也就是我们说的,应该把一个类或对象它所有相关的属性.方法.行为放到一起,放到一个类中,这样就实现了一个封装的特性.那么内聚,就是一个类里面应该包含它所有的属性和

  • 浅谈C# 抽象类与开闭原则

    1.抽象类与抽象方法: (1)使用关键字abstract修饰的类,称为抽象类. (2)抽象类只是用到一个类所具有的行为,不能单独通过创建对象来使用.使用new是错误的.可以通过派生类来实现其函数成员的具体逻辑. (3)抽象类中可以有抽象方法,也可以没有任何抽象方法.只要类中存在一个抽象方法,这个类就是抽象类. (4)抽象类不能是静态的(static)或者密封的(sealed) 下面就是一个简单的抽象类 abstract class Vehicle { public abstract void t

  • C# 实例解释面向对象编程中的单一功能原则(示例代码)

    在面向对象编程领域中,单一功能原则(Single responsibility principle)规定每个类都应该有且仅有一个单一的功能,并且该功能应该由这个类完全封装起来. 在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集,在他2000年的论文<设计原则与设计模式>中首次提出. SOLID 原则包含: S:单一功能原则(single

  • C#面向对象设计原则之开闭原则

    开闭原则(OCP) 定义:对扩展开发,对修改关闭.好处: 适应性和灵活性. 稳定性和延续性. 可复用性与可维护性. 解释说明:开闭原则指的是两方面:对功能扩展开发,对修改进行关闭:有时当用户要求或需求发生变化时,我们不得不打开原来的代码进行修改,进行功能的扩展或增加,这种设计如果应用到我们以后的项目开发中会导致严重的问题,这样容易导致意外的错误.好的程序,应该保证在我们进行程序扩展时,不会更改以前的代码.如何才能保证这样的效果呢?我们在定义一个类的功能时:最好先定义他的抽象类或接口,这样在功能扩

  • C#面向对象设计原则之组合/聚合复用原则

    组合/聚合复用原则(LSP) 定义:优先使用组合,使系统更灵活,其次才考虑继承,达到复用的目的.重用的方式:继承.组合.聚合解释说明: 继承:在我们想复用代码时,我们一般会优先想到继承,但是具有继承关系的两个类是耦合度最高的两个类.(父类改了子类可能会受影响,子类改了父类也可能会受影响)如果父类的功能比较稳定,建议使用继承来实现代码复用,因为继承是静态定义的,在运行时无法动态调用. 组合:是整体与部分的关系,整体离不开部分,部分离开了整体没有意义,如飞机翅膀与飞机的关系. 聚合:也是整体与部分的

  • C#面向对象编程中开闭原则的示例详解

    目录 开闭原则 C# 示例 改进 总结 在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集,在他2000年的论文<设计原则与设计模式>中首次提出. SOLID 原则包含: S:单一功能原则(single-responsibility principle) O:开闭原则(open-closed principle) L:里氏替换原则(Lis

  • C#面向对象编程中接口隔离原则的示例详解

    目录 接口隔离原则 C# 示例 糟糕的示范 正确的示范 总结 在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集,在他2000年的论文<设计原则与设计模式>中首次提出. SOLID 原则包含: S:单一功能原则(single-responsibility principle) O:开闭原则(open-closed principle) L

  • C#面向对象编程中里氏替换原则的示例详解

    目录 里氏替换原则 C# 示例 糟糕的示范 正确的示范 总结 在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集,在他2000年的论文<设计原则与设计模式>中首次提出. SOLID 原则包含: S:单一功能原则(single-responsibility principle) O:开闭原则(open-closed principle) L

  • C#面向对象编程中依赖反转原则的示例详解

    在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原则的子集,在他2000年的论文<设计原则与设计模式>中首次提出. SOLID 原则包含: S:单一功能原则(single-responsibility principle) O:开闭原则(open-closed principle) L:里氏替换原则(Liskov substitution pri

  • Java面向对象编程中final关键字的使用方法详解

    在Java中通过final关键字来声明对象具有不变性(immutable),这里的对象包括变量,方法,类,与C++中的const关键字效果类似. immutable指对象在创建之后,状态无法被改变 可以从三个角度考虑使用final关键字: 代码本身:不希望final描述的对象所表现的含义被改变 安全:final对象具有只读属性,是线程安全的 效率:无法修改final对象本身,对其引用的操作更为高效 final 变量 定义final Object a,则a只能被初始化一次,一旦初始化,a的数据无法

  • Java编程中的4种代码块详解

    在Java编程中,代码块就是指用"{}"括起来的代码.下面看一下这四种代码块. 1.普通代码块 就是指类中方法的方法体. public void xxx(){ //code } 2.构造块 下面用"{}"括起来的代码片段,构造块在创建对象时会被调用,每次创建对象时都会被调用,并且优先于类构造函数(包括有参和无参的)执行. 构造块中定义的变量是局部变量. public class Client { {//构造代码块 System.out.println("执

  • Python编程中*args与**kwargs区别作用详解

    相信学Python的小伙伴肯定有这样的尴尬局面,给一个函数不会用, 原因是:不知道参数列表中的类型是什么意思,比如初学者都会疑问的:*args和**kwargs到底是怎么用. 当你知道这个时,我猜你肯定能会用好多函数了! #*args的用法:当传入的参数个数未知,且不需要知道参数名称时. def func_arg(farg, *args): print("formal arg:", farg) for arg in args: print("another arg:"

  • C语言编程C++旋转字符操作串示例详解

    目录 旋转字符串 字符串左旋 题前认知: 暴力移位: 三步翻转: 判断字符串旋转 题前认知 字符串追加判断 旋转字符串 字符串左旋 实现一个函数,可以左旋字符串中的k个字符. 例如: ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 题前认知: 一个字符串如果就定死了.eg:char arr[]="dfdf"什么的那多没意思,一点都没有人机交互的感觉,(虽然现在人机交互适合个体,不适合集群,但也是比死板的定死字符串舒服) 所以字符串得是我们可输入的,才有可玩性,玩的不

  • python编程普通及类和静态方法示例详解

    目录 前言 运行环境 普通方法 类方法 静态方法 前言 本文主要讲述了python类中的三类常用方法,普通方法.类方法和静态方法.  本文主要参考了https://youtu.be/rq8cL2XMM5M,强烈推荐一看这个系列的所有视频. 运行环境 import sys sys.version 结果为 '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]' 普通方

  • 详解Python中生成随机数据的示例详解

    目录 随机性有多随机 加密安全性 PRNG random 模块 数组 numpy.random 相关数据的生成 random模块与NumPy对照表 CSPRNG 尽可能随机 os.urandom() secrets 最佳保存方式 UUID 工程随机性的比较 在日常工作编程中存在着各种随机事件,同样在编程中生成随机数字的时候也是一样,随机有多随机呢?在涉及信息安全的情况下,它是最重要的问题之一.每当在 Python 中生成随机数据.字符串或数字时,最好至少大致了解这些数据是如何生成的. 用于在 P

随机推荐