文章目录
- 前提:
- 一、特性:
- 二、原则:
- 三、示例
- 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)的原则和特性如下所示:
一、特性:
-
类与对象:
- 类是对象的抽象描述,它定义了对象的属性和行为。对象是类的实例,具体化了类的属性和行为。
-
方法(Method):
- 方法是类中的函数,用于执行特定的操作。方法通常用于操作对象的状态或提供对象的行为。
-
继承与派生:
- 继承允许新类(子类)基于现有类(父类)的定义来创建。子类可以继承父类的属性和方法,并且可以添加新的属性和方法。
-
多态性:
- 多态性允许不同类的对象对同一消息作出不同的响应。这提高了代码的灵活性和可维护性。
-
抽象(Abstraction):
- 抽象是指将复杂的现实世界问题简化为程序设计中的对象模型。通过抽象,程序员可以专注于对象的关键特征,并忽略不相关的细节。
-
封装性:
- 封装性通过将数据和方法组合成一个单元,并限制对数据的直接访问,保护了对象的状态。这样可以防止意外修改对象的状态,提高了代码的可靠性和安全性。
-
消息传递(Message Passing):
- 对象之间的通信是通过发送消息来实现的。对象通过调用其他对象的方法来发送消息,从而实现相互之间的交互和协作。
其中我们所提及的三大特性主要指的是:
-
封装(Encapsulation):
- 封装将数据和行为组合成一个单一的单元,并将其限制在类的内部。对象的内部状态只能通过公共接口访问,而不是直接暴露给外部。
-
继承(Inheritance):
- 继承允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以使用父类的属性和方法,并且可以添加自己的特定功能。
-
多态(Polymorphism):
- 多态允许对象在运行时表现出不同的行为。即使是相同的方法调用,具体执行的操作也可能因对象的类型而异。这提高了代码的灵活性和可重用性。
这些原则和特性共同构成了面向对象编程范式的核心。通过遵循这些原则和特性,开发人员可以编写出结构清晰、可维护、可扩展的代码。
二、原则:
在面向对象编程中,编写的代码应该符合以下原则:
-
单一职责原则(Single Responsibility Principle, SRP):
- 一个类应该只有一个引起变化的原因。换句话说,一个类应该只负责一项任务。这样可以提高代码的内聚性,降低类的复杂度,使代码更容易理解和维护。
-
开放-封闭原则(Open-Closed Principle, OCP):
- 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的情况下,应该能够通过扩展来添加新的功能。通过使用抽象类、接口和多态等技术,可以实现对修改关闭的设计。
-
里氏替换原则(Liskov Substitution Principle, LSP):
- 所有引用基类(父类)的地方必须能够透明地使用其子类的对象,而不会影响程序的正确性。这意味着子类必须能够完全替代其父类,而不引起意外行为。
-
接口隔离原则(Interface Segregation Principle, ISP):
- 不应该强迫客户端依赖于它们不使用的接口。接口应该尽可能小,只包含客户端需要的方法。这样可以避免不必要的依赖,并使系统更加灵活和易于维护。
-
依赖倒置原则(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
类,以及具体的子类 Circle
和 Rectangle
。通过这种方式,我们可以通过添加新的形状类来扩展系统,而不需要修改现有的代码。
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
类的子类,因为它改变了父类中的行为。通过将 Rectangle
和 Square
都实现为 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
接口拆分为 IWorker
、IBreak
和 ITimeClock
接口,以更好地符合接口隔离原则。每个接口都代表一个独立的功能领域,使得类只需要实现其相关的接口。
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