什么是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原则,可以显著提高代码的可维护性、可扩展性和灵活性,使开发和维护变得更加高效。