C#面向对象编程中开闭原则的示例详解
作者:技术译民 发布时间:2022-12-07 11:15:28
在面向对象编程中,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
现在,这些类的设计,既遵循了单一功能原则,又遵循了开闭原则。
来源:https://www.cnblogs.com/ittranslator/p/SOLID-open-closed-principle.html
猜你喜欢
- 类加载是什么把磁盘中的java文件加载到内存中的过程叫做类加载当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把
- 前言前面的篇幅里有提到通过InitializingBean和Disposable等接口可以对bean的初始化和销毁做一些自定义操作,那么有一
- 本文主要讨论C#开发使用百度语音合成API在线将文本内容合成语音,保存为 MP3 文件,本文最后会提供本安全源代码以及运行软件包,
- 我们springboot项目有自己默认的配置文件,一般地由application.yml和bootstrap.yml组成,前者是模块的配置,
- 一、分布式压测原理如下图(这个图说明的是要一台控制机,然后由这台控制机发压测脚本到每台远程执行机,然后由控制机收集执行机结果)二、修改 Jm
- 目录前言1. 如何让两个线程依次执行?2. 如何让两个线程按照指定的方式有序相交?3. 线程 D 在A、B、C都同步执行完毕后执行4. 三个
- java中的线程状态🥇在操作系统层面,一个线程就两个状态:就绪和阻塞状态.但是java中为了在线程阻塞时能够更快速的知晓一个线程阻塞的原因,
- 本文为大家分享了使用静态关键字实现单例模式的具体代码,供大家参考,具体内容如下单例模式:只能获得某个类的唯一一个实例单例模式,不管什么时间点
- 通用配置#下面介绍的整合JDBC和整合MyBatis都需要添加的实体类和配置数据库表#CREATE TABLE `user` ( `id`
- HttpServletRequest对象代表客户端的请求,当客户端通过HTTP
- 概念尽量使用合成/聚合,而不是使用继承实现复用。所谓的合成/聚合是指一个对象里持有另外一个类的对象,通过调用这些对象的方法得到复用已有功能的
- 我们平时在日常项目中经常会遇到图片的上传和访问的情景,平时我们可能习惯于把图片传到resource或者项项目中的某个位置,这样会有一个缺点,
- 什么是响应式简单来说当数据发生变化时,对数据有依赖的代码会重新执行。例如在Vue中,当我们的数据发生改变,界面上对该数据的引用组件会重新渲染
- 使用filter对response内容进行加密编写加密类(AES)/** * aes加密解密 */public class AesEncry
- 项目中肯定会遇到异步调用其他方法的场景,比如有个计算过程,需要计算很多个指标的值,但是每个指标计算的效率快慢不同,如果采用同步执行的方式,运
- 出处:https://www.cnblogs.com/SunSpring作为初学者配置maven一般网上搜索。然后你就看到各种配置文件片段,
- BeanFactory接口:IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工
- (Memory Leak,内存泄漏)为什么会产生内存泄漏?当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致
- 本文实例为大家分享了C#实现简易点餐功能的具体代码,供大家参考,具体内容如下图示效果实现过程1.设计界面2.设计控件及其属性3.实现点击事件
- 原文地址:http://www.javayihao.top/detail/84一:概述由于springboot项目,不管是java工程还是w