C#探索之路基础夯实篇(3):面向对象的三大特性和五大原则详解

news2024/11/18 10:38:18

文章目录

      • 前提:
      • 一、特性:
      • 二、原则:
      • 三、示例
        • 1. 单一职责原则 (Single Responsibility Principle, SRP):
        • 2. 开放-封闭原则 (Open-Closed Principle, OCP):
        • 3. 里氏替换原则 (Liskov Substitution Principle, LSP):
        • 4. 接口隔离原则 (Interface Segregation Principle, ISP):
        • 5. 依赖倒置原则 (Dependency Inversion Principle, DIP):

前提:

在维护旧功能和扩展功能的时候,我时常发现一个维护代码代价的痛点,尤其是上一个写当前功能的人不是我的时候,维护起来就需要看到别人代码的扩展性是否足够高,是否便于进行二次开发。这个就让我想起来了编程原则。
面向对象编程(Object-Oriented Programming, OOP)的原则和特性如下所示:

一、特性:

  1. 类与对象

    • 类是对象的抽象描述,它定义了对象的属性和行为。对象是类的实例,具体化了类的属性和行为。
  2. 方法(Method)

    • 方法是类中的函数,用于执行特定的操作。方法通常用于操作对象的状态或提供对象的行为。
  3. 继承与派生

    • 继承允许新类(子类)基于现有类(父类)的定义来创建。子类可以继承父类的属性和方法,并且可以添加新的属性和方法。
  4. 多态性

    • 多态性允许不同类的对象对同一消息作出不同的响应。这提高了代码的灵活性和可维护性。
  5. 抽象(Abstraction)

    • 抽象是指将复杂的现实世界问题简化为程序设计中的对象模型。通过抽象,程序员可以专注于对象的关键特征,并忽略不相关的细节。
  6. 封装性

    • 封装性通过将数据和方法组合成一个单元,并限制对数据的直接访问,保护了对象的状态。这样可以防止意外修改对象的状态,提高了代码的可靠性和安全性。
  7. 消息传递(Message Passing)

    • 对象之间的通信是通过发送消息来实现的。对象通过调用其他对象的方法来发送消息,从而实现相互之间的交互和协作。

    其中我们所提及的三大特性主要指的是:

    1. 封装(Encapsulation)

      • 封装将数据和行为组合成一个单一的单元,并将其限制在类的内部。对象的内部状态只能通过公共接口访问,而不是直接暴露给外部。
    2. 继承(Inheritance)

      • 继承允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以使用父类的属性和方法,并且可以添加自己的特定功能。
    3. 多态(Polymorphism)

      • 多态允许对象在运行时表现出不同的行为。即使是相同的方法调用,具体执行的操作也可能因对象的类型而异。这提高了代码的灵活性和可重用性。

这些原则和特性共同构成了面向对象编程范式的核心。通过遵循这些原则和特性,开发人员可以编写出结构清晰、可维护、可扩展的代码。

二、原则:

在面向对象编程中,编写的代码应该符合以下原则:

  1. 单一职责原则(Single Responsibility Principle, SRP)

    • 一个类应该只有一个引起变化的原因。换句话说,一个类应该只负责一项任务。这样可以提高代码的内聚性,降低类的复杂度,使代码更容易理解和维护。
  2. 开放-封闭原则(Open-Closed Principle, OCP)

    • 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的情况下,应该能够通过扩展来添加新的功能。通过使用抽象类、接口和多态等技术,可以实现对修改关闭的设计。
  3. 里氏替换原则(Liskov Substitution Principle, LSP)

    • 所有引用基类(父类)的地方必须能够透明地使用其子类的对象,而不会影响程序的正确性。这意味着子类必须能够完全替代其父类,而不引起意外行为。
  4. 接口隔离原则(Interface Segregation Principle, ISP)

    • 不应该强迫客户端依赖于它们不使用的接口。接口应该尽可能小,只包含客户端需要的方法。这样可以避免不必要的依赖,并使系统更加灵活和易于维护。
  5. 依赖倒置原则(Dependency Inversion Principle, DIP)

    • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。通过依赖注入和依赖倒置容器等技术,可以实现低耦合和高内聚的设计。

遵循这些原则可以帮助开发人员编写出高质量、可维护、可扩展的面向对象代码,提高代码的质量和可靠性。

三、示例

以下是针对每种原则的具体C#代码示例:

1. 单一职责原则 (Single Responsibility Principle, SRP):
using System;

// Violating SRP 违背单一职责原则,类的职责不再单一
class Car {
    public void StartEngine() {
        Console.WriteLine("Engine started");
    }
    
    public void Drive() {
        Console.WriteLine("Car is driving");
    }
    
    public void PlayMusic() {
        Console.WriteLine("Playing music");
    }
}

// Following SRP 遵守单一职责的原则,类的职责依旧单一
class Car {
    public void StartEngine() {
        Console.WriteLine("Engine started");
    }
    
    public void Drive() {
        Console.WriteLine("Car is driving");
    }
}

class MusicPlayer {
    public void PlayMusic() {
        Console.WriteLine("Playing music");
    }
}

// Client code
class Program {
    static void Main(string[] args) {
        Car car = new Car();
        car.StartEngine();
        car.Drive();
        
        MusicPlayer player = new MusicPlayer();
        player.PlayMusic();
    }
}

在这个例子中,Car 类负责汽车的启动和驾驶,而 MusicPlayer 类负责音乐播放。这样,每个类都只有一个单一的职责。

2. 开放-封闭原则 (Open-Closed Principle, OCP):
using System;

// Violating OCP 违背了开放-封闭原则
class Shape {
    public string Type { get; set; }
    public void Draw() {
        if (Type == "Circle") {
            Console.WriteLine("Drawing circle");
        } else if (Type == "Rectangle") {
            Console.WriteLine("Drawing rectangle");
        }
    }
}

// Following OCP 遵守开放-封闭原则
abstract class Shape {
    public abstract void Draw();
}

class Circle : Shape {
    public override void Draw() {
        Console.WriteLine("Drawing circle");
    }
}

class Rectangle : Shape {
    public override void Draw() {
        Console.WriteLine("Drawing rectangle");
    }
}

// Client code
class Program {
    static void Main(string[] args) {
        Shape circle = new Circle();
        circle.Draw();
        
        Shape rectangle = new Rectangle();
        rectangle.Draw();
    }
}

在这个例子中,我们定义了一个抽象的 Shape 类,以及具体的子类 CircleRectangle。通过这种方式,我们可以通过添加新的形状类来扩展系统,而不需要修改现有的代码。

3. 里氏替换原则 (Liskov Substitution Principle, LSP):
using System;

// Violating LSP 违背里氏替换原则
class Rectangle {
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }

    public int CalculateArea() {
        return Width * Height;
    }
}

class Square : Rectangle {
    public override int Width {
        set { base.Width = base.Height = value; }
    }
    
    public override int Height {
        set { base.Width = base.Height = value; }
    }
}

// Following LSP  遵守里氏替换原则
abstract class Shape {
    public abstract int CalculateArea();
}

class Rectangle : Shape {
    public int Width { get; set; }
    public int Height { get; set; }

    public override int CalculateArea() {
        return Width * Height;
    }
}

class Square : Shape {
    public int SideLength { get; set; }

    public override int CalculateArea() {
        return SideLength * SideLength;
    }
}

// Client code
class Program {
    static void Main(string[] args) {
        Shape rectangle = new Rectangle { Width = 3, Height = 4 };
        Console.WriteLine("Rectangle area: " + rectangle.CalculateArea());
        
        Shape square = new Square { SideLength = 5 };
        Console.WriteLine("Square area: " + square.CalculateArea());
    }
}

在这个例子中,Square 类不再是 Rectangle 类的子类,因为它改变了父类中的行为。通过将 RectangleSquare 都实现为 Shape 的子类,我们遵循了里氏替换原则。

4. 接口隔离原则 (Interface Segregation Principle, ISP):
using System;

// Violating ISP 违背了接口隔离原则,使得接口的含义不明确
interface IWorker {
    void Work();
    void TakeBreak();
    void ClockIn();
    void ClockOut();
}

class Worker : IWorker {
    public void Work() {
        Console.WriteLine("Working");
    }
    
    public void TakeBreak() {
        Console.WriteLine("Taking a break");
    }
    
    public void ClockIn() {
        Console.WriteLine("Clocking in");
    }
    
    public void ClockOut() {
        Console.WriteLine("Clocking out");
    }
}

// Following ISP 遵守了接口隔离原则
interface IWorker {
    void Work();
}

interface IBreak {
    void TakeBreak();
}

interface ITimeClock {
    void ClockIn();
    void ClockOut();
}

class Worker : IWorker, IBreak, ITimeClock {
    public void Work() {
        Console.WriteLine("Working");
    }
    
    public void TakeBreak() {
        Console.WriteLine("Taking a break");
    }
    
    public void ClockIn() {
        Console.WriteLine("Clocking in");
    }
    
    public void ClockOut() {
        Console.WriteLine("Clocking out");
    }
}

// Client code
class Program {
    static void Main(string[] args) {
        Worker worker = new Worker();
        worker.Work();
        worker.TakeBreak();
        worker.ClockIn();
        worker.ClockOut();
    }
}

在这个例子中,我们将 IWorker 接口拆分为 IWorkerIBreakITimeClock 接口,以更好地符合接口隔离原则。每个接口都代表一个独立的功能领域,使得类只需要实现其相关的接口。

5. 依赖倒置原则 (Dependency Inversion Principle, DIP):
using System;

// Violating DIP 
class LightBulb {
    public void TurnOn() {
        Console.WriteLine("Light bulb turned on");
    }
}

class LightSwitch {
    private LightBulb bulb = new LightBulb();

    public void Flip() {
        bulb.TurnOn();
    }
}

// Following DIP
interface ISwitchable {
    void TurnOn();
}

class LightBulb : ISwitchable {
    public void TurnOn() {
        Console.WriteLine("Light bulb turned on");
    }
}

class Fan : ISwitchable {
    public void TurnOn() {
        Console.WriteLine("Fan turned on");
    }
}

class Switch {
    private ISwitchable device;

    public Switch(ISwitchable device) {
        this.device = device;
    }

    public void Flip() {
        device.TurnOn();
    }


}

// Client code
class Program {
    static void Main(string[] args) {
        ISwitchable bulb = new LightBulb();
        Switch lightSwitch = new Switch(bulb);
        lightSwitch.Flip();
        
        ISwitchable fan = new Fan();
        Switch fanSwitch = new Switch(fan);
        fanSwitch.Flip();
    }
}

在这个例子中,Switch 类不再直接依赖于 LightBulb 类,而是依赖于 ISwitchable 接口。这样,我们可以在不修改 Switch 类的情况下轻松地将其用于控制其他可开关的设备,符合依赖倒置原则。

这些示例演示了如何在C#中应用面向对象编程原则。通过遵循这些原则,可以编写出更加模块化、灵活和可维护的代码。

在这里插入图片描述

公众号:平平无奇代码猴
也可以搜索:Jackiie_wang 公众号,欢迎大家关注!欢迎催更!留言!

作者:ProMer_Wang

链接:https://blog.csdn.net/qq_43801020/article/details/137443640

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1573546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

移动硬盘能当u盘重装系统吗?做启动数据还能恢复吗

在数字时代,随着技术的不断发展,我们越来越依赖于各种存储设备,其中移动硬盘以其大容量和便携性受到广大用户的青睐。不过,有时我们可能会遇到需要使用U盘来重装系统的情况,而手头又没有合适的U盘。这时,我…

Spring Boot-01-通过一个项目快速入门

官方参考文档:Spring Boot Reference Documentation 0. 概述 Spring的缺点: 1. 配置繁琐:虽然Spring的组件代码是轻量级,但它的配置却是重量级的。 2. 依赖繁琐:项目的依赖管理也是一件耗时耗力的事情。分析要导入哪…

03-JAVA设计模式-工厂模式详解

工厂模式 工厂设计模式是一种创建型设计模式,它提供了一种封装对象创建过程的机制,将对象的创建与使用分离。 这种设计模式允许我们在不修改客户端代码的情况下引入新的对象类型。 在Java中,工厂设计模式主要有三种形式:简单工厂…

【CNN】ConvMixer探究ViT的Patch Embedding: Patches Are All You Need?

Patches Are All You Need? 探究Patch Embedding在ViT上的作用,CNN是否可用该操作提升性能? 论文链接:https://openreview.net/pdf?idTVHS5Y4dNvM 代码链接:https://github.com/tmp-iclr/convmixer 1、摘要 ViT的性能是由于T…

基于Spark中随机森林模型的天气预测系统

基于Spark中随机森林模型的天气预测系统 在这篇文章中,我们将探讨如何使用Apache Spark和随机森林算法来构建一个天气预测系统。该系统将利用历史天气数据,通过机器学习模型预测未来的天气情况,特别是针对是否下雨的二元分类问题。 简介 Ap…

【unity】【C#】延时调用(协程)和场景管理

文章目录 什么是协程协程的应用 - IEnumerator如何控制协程的暂停协程的另一种写法 - Invoke场景管理 多看代码块中的注释 什么是协程 A coroutine alows vou to spreacwhere it left off on the following anc return control toolinencoeframe. 协程允许您将任务分布在多个帧…

Android14应用启动流程(源码+Trace)

1.简介 应用启动过程快的都不需要一秒钟,但这整个过程的执行是比较复杂的,无论是对手机厂商、应用开发来说启动速度也是核心用户体验指标之一,本文采用Android14源码与perfetto工具进行解析。 源码参考地址:Search trace分析工…

【二分查找】Leetcode 在排序数组中查找元素的第一个和最后一个位置

题目解析 34. 在排序数组中查找元素的第一个和最后一个位置 我们使用暴力方法进行算法演化,寻找一个数字的区间,我们可以顺序查找,记录最终结果 首先数组是有序的,所以使用二分法很好上手,但是我们就仅仅使用上一道题…

第四百四十二回 再谈flutter_launcher_icons包

文章目录 1. 概念介绍2. 使用方法3. 示例代码4. 经验与总结4.1 经验分享4.2 内容总结 我们在上一章回中介绍了"overlay_tooltip简介"相关的内容,本章回中将 再谈flutter_launcher_icons包.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 …

配置vscode链接linux

1.安装 remote SSH 2.按F1 ssh ljh服务器公网ip 3. 选择保存远端host到本地 某位置 等待片刻后 4. 切换到远程资源管理器中 应该可以看到一台电脑,右键在当前窗口链接,输入你的服务器用户密码后电脑变绿说明远程连接成功 5.一定要登陆上云服务器后再…

Day:004(2) | Python爬虫:高效数据抓取的编程技术(数据解析)

正则表达式实战-腾讯新闻 需求: 使用正则获取腾讯新闻标题内容 网站:https://sports.qq.com/ 代码: import reimport requests from fake_useragent import UserAgenturl https://sports.qq.com/ # 构建请求头信息 headers {User-Agent:…

【JavaWeb】Day33.MySQL概述

什么是数据库 数据库:英文为 DataBase,简称DB,它是存储和管理数据的仓库。 像我们日常访问的电商网站京东,企业内部的管理系统OA、ERP、CRM这类的系统,以及大家每天都会刷的头条、抖音类的app,那这些大家所…

前端学习之DOM编程星星点灯案例

这个案例的实现逻辑是当你点击屏幕时&#xff0c;会完成一个事件&#xff0c;在你的屏幕上生成一张星星图片。然后再设置星星图片的大小将其改为随机。 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><t…

K8S基于containerd做容器从harbor拉取镜

实现创建pod时&#xff0c;通过指定harbor仓库里的镜像来运行pod 检查&#xff1a;K8S是不是用containerd做容器运行时&#xff0c;以及containerd的版本是不是小于1.6.22 kubectl get nodes -owide1、如果containerd小于 1.6.22&#xff0c;需要先升级containerd 先卸载旧的…

C/C++预处理过程

目录 前言&#xff1a; 1. 预定义符号 2. #define定义常量 3. #define定义宏 4. 带有副作用的宏参数 5. 宏替换的规则 6. 宏和函数的对比 7. #和## 8. 命名约定 9. #undef 10. 命令行定义 11. 条件编译 12. 头文件的包含 13. 其他预处理指令 总结&#x…

最新高自定义化的AI翻译(沉浸式翻译),可翻译网页和PDF等文件或者文献(附翻译API总结,Deeplx的api,Deepl的api)

前序 常见问题&#xff1a; 1.有时候想翻译网页&#xff0c;又翻译文献怎么办&#xff1f;下两个软件&#xff1f; 2.什么软件可以翻译视频字幕&#xff1f; 3.什么软件可以翻译PDF文件&#xff1f; 沉浸式翻译介绍 可以翻译文献可以翻译视频字幕可以翻译PDF文件支持OpenAI翻译…

7 个 iMessage 恢复应用程序/软件可轻松恢复文本

由于误操作、iOS 升级中断、越狱失败、设备损坏等原因&#xff0c;您可能会丢失 iPhone/iPad 上的 iMessages。意外删除很大程度上增加了这种可能性。更糟糕的是&#xff0c;这种情况经常发生在 iDevice 缺乏备份的情况下。 &#xff08;iPhone消息消失还占用空间&#xff1f;&…

很多名人让人们警惕人工智能,这是为何?

很多名人让人们警惕人工智能的原因可以从多个角度来理解。首先&#xff0c;人工智能作为一个快速发展的领域&#xff0c;具有巨大的潜力和未知性。它涉及到机器学习、深度学习、神经网络等多个复杂的技术领域&#xff0c;而这些技术正日益渗透到我们的日常生活中&#xff0c;从…

JWT/JWS/JWE

JWT(JSON Web Token)&#xff1a;一种基于JSON格式&#xff0c;用于在Web应用中安全传递用户身份验证和授权信息的标准令牌&#xff0c;可以包含签名(JWS)和加密(JWE)的信息 MacAlgorithm(Message Authentication Code Algorithm)&#xff1a;消息认证码算法 HMAC(Hash-based…

【51单片机入门记录】RTC(实时时钟)-DS1302应用

目录 一、DS1302相关写函数 &#xff08;1&#xff09;Write&#xff3f;Ds1302 &#xff08;2&#xff09;Write&#xff3f;Ds1302&#xff3f;Byte 二、DS130相关数据操作流程及相关代码 &#xff08;1&#xff09;DS1302初始化数据操作流程及相关代码 (shijian[i]/10&…