什么是C#的五大设计原则,我们用人话来解释一下,希望小伙伴们能学会:
好的,让我们以一种幽默的方式来解释C#的五大设计原则(SOLID):
单一职责原则(Single Responsibility Principle, SRP):别让一个类做得像瑞士军刀一样样样都干,它会累趴下的。让每个类专注于一件事,成就一个小专家!
开闭原则(Open/Closed Principle, OCP):代码就像房子一样,应该允许你随时装修,但不要让你每次都得拆墙。你的类应该开放给扩展,关闭给修改。装修可以,但别动地基。
里氏替换原则(Liskov Substitution Principle, LSP):如果它看起来像鸭子,游起来像鸭子,叫起来也像鸭子,那它就应该是个鸭子。子类应该能完全替代父类,就像鸭子替代鸭子一样。
接口隔离原则(Interface Segregation Principle, ISP):不要逼用户点一堆不想要的菜,分开菜单让他们点自己喜欢的。让接口精简,用户用起来才不会吐槽你。
依赖倒置原则(Dependency Inversion Principle, DIP):别总想着亲自动手干活,雇个专业的帮你干!高层模块不应该依赖低层模块,而应该依赖于抽象。让专业的人做专业的事,你只需要指挥就好。
1. 单一职责原则(SRP - Single Responsibility Principle)
不使用SRP:
public class UserService
{
public void RegisterUser(string name, string email)
{
// 保存用户到数据库
SaveUserToDatabase(name, email);
// 发送欢迎邮件
SendWelcomeEmail(email);
}
private void SaveUserToDatabase(string name, string email)
{
// 代码逻辑
}
private void SendWelcomeEmail(string email)
{
// 代码逻辑
}
}
在这个示例中,UserService
类同时处理用户注册和发送邮件,职责不单一,导致类的复杂性增加,不利于维护和扩展。
使用SRP:
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
public class UserRepository
{
public void AddUser(User user) { /* 代码 */ }
public User GetUserByEmail(string email) { /* 代码 */ }
}
public class EmailService
{
public void SendEmail(User user) { /* 代码 */ }
}
public class UserService
{
private readonly UserRepository _userRepository;
private readonly EmailService _emailService;
public UserService(UserRepository userRepository, EmailService emailService)
{
_userRepository = userRepository;
_emailService = emailService;
}
public void RegisterUser(User user)
{
_userRepository.AddUser(user);
_emailService.SendEmail(user);
}
}
在这个示例中,职责被分离到了不同的类中,UserService
只负责协调这些操作,使代码更清晰、可维护。
2. 开闭原则(OCP - Open/Closed Principle)
不使用OCP:
public class Shape
{
public double Radius { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double GetArea(string shapeType)
{
if (shapeType == "Circle")
{
return Math.PI * Radius * Radius;
}
else if (shapeType == "Rectangle")
{
return Width * Height;
}
else
{
return 0;
}
}
}
在这个示例中,添加新的形状类型需要修改GetArea
方法的代码,违反了开闭原则。
使用OCP:
public abstract class Shape
{
public abstract double Area();
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area() => Math.PI * Radius * Radius;
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area() => Width * Height;
}
在这个示例中,通过扩展Shape
类,我们可以添加新的形状而不需要修改现有的代码,符合开闭原则。
3. 里氏替换原则(LSP - Liskov Substitution Principle)
不使用LSP:
public class Rectangle
{
public virtual double Width { get; set; }
public virtual double Height { get; set; }
public double Area() => Width * Height;
}
public class Square : Rectangle
{
public override double Width
{
set { base.Width = base.Height = value; }
}
public override double Height
{
set { base.Width = base.Height = value; }
}
}
在这个示例中,Square
类违反了里氏替换原则,因为Width
和Height
的行为在子类中改变了。
使用LSP:
public abstract class Shape
{
public abstract double Area();
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area() => Width * Height;
}
public class Square : Shape
{
public double SideLength { get; set; }
public override double Area() => SideLength * SideLength;
}
在这个示例中,Rectangle
和Square
都正确实现了Shape
的Area
方法,符合里氏替换原则。
4. 接口隔离原则(ISP - Interface Segregation Principle)
不使用ISP:
public interface IWorker
{
void Work();
void Eat();
}
public class Worker : IWorker
{
public void Work() { /* 代码逻辑 */ }
public void Eat() { /* 代码逻辑 */ }
}
public class Robot : IWorker
{
public void Work() { /* 代码逻辑 */ }
public void Eat()
{
throw new NotImplementedException();
}
}
在这个示例中,Robot
类必须实现它不需要的Eat
方法,导致接口不符合实际需求。
使用ISP:
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public class Worker : IWorkable, IEatable
{
public void Work() { /* 代码逻辑 */ }
public void Eat() { /* 代码逻辑 */ }
}
public class Robot : IWorkable
{
public void Work() { /* 代码逻辑 */ }
}
在这个示例中,IWorkable
和IEatable
接口被分离,Robot
类只实现了需要的接口,符合接口隔离原则。
5. 依赖倒置原则(DIP - Dependency Inversion Principle)
不使用DIP:
public class DatabaseLogger
{
public void Log(string message)
{
// 记录日志到数据库
}
}
public class UserService
{
private readonly DatabaseLogger _logger = new DatabaseLogger();
public void RegisterUser(string name, string email)
{
// 记录日志
_logger.Log("Registering user " + name);
// 其他代码
}
}
在这个示例中,UserService
类直接依赖于具体的DatabaseLogger
实现,违反了依赖倒置原则。
使用DIP:
public interface ILogger
{
void Log(string message);
}
public class DatabaseLogger : ILogger
{
public void Log(string message)
{
// 记录日志到数据库
}
}
public class FileLogger : ILogger
{
public void Log(string message)
{
// 记录日志到文件
}
}
public class UserService
{
private readonly ILogger _logger;
public UserService(ILogger logger)
{
_logger = logger;
}
public void RegisterUser(string name, string email)
{
// 记录日志
_logger.Log("Registering user " + name);
// 其他代码
}
}
在这个示例中,UserService
类依赖于ILogger
接口,而不是具体的实现,符合依赖倒置原则,使得代码更加灵活和可扩展。
总结对比
- 单一职责原则(SRP): 通过分离职责减少类的复杂度,使代码更易读和维护。不使用SRP会导致类变得庞大,职责不清,维护困难。
- 开闭原则(OCP): 通过扩展来增加新功能,而不是修改已有代码,提高了代码的稳定性和可扩展性。不使用OCP会导致每次增加新功能都需要修改已有代码,增加了出错的风险。
- 里氏替换原则(LSP): 确保子类可以替代基类而不会导致程序出错,保证继承的正确性。不使用LSP会导致子类行为不一致,破坏程序的稳定性。
- 接口隔离原则(ISP): 通过细化接口,使得类只依赖于需要的接口,减少不必要的依赖关系。不使用ISP会导致类实现不需要的接口方法,增加了代码的复杂度。
- 依赖倒置原则(DIP): 通过依赖于抽象(接口或抽象类)而不是具体实现,降低模块之间的耦合,提高代码的灵活性和可测试性。不使用DIP会导致类之间的高耦合,难以进行单元测试和模块替换。
通过遵循SOLID原则,可以显著提高代码的可维护性、可扩展性和灵活性,使开发和维护变得更加高效。