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

前提:

在维护旧功能和扩展功能的时候,我时常发现一个维护代码代价的痛点,尤其是上一个写当前功能的人不是我的时候,维护起来就需要看到别人代码的扩展性是否足够高,是否便于进行二次开发。这个就让我想起来了编程原则。
面向对象编程(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

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值