SOLID:接口隔离原则
接口隔离原则(ISP)是 SOLID 原则的第四个原则。它可以与 LSP 结合使用。
接口隔离原则(ISP)规定:
现在,你可能会想,谁是客户端,它指的是什么方法以及谁的方法?
在这里,客户端是调用类方法的代码,通过接口的实例。例如,一个类实现了一个包含 10 个方法的接口。现在,你使用该接口的变量创建该类的一个对象,并仅调用 5 个方法以实现所需的功能,而从不调用其他 5 个方法。所以,这意味着该接口包含更多不被所有客户端代码使用的方法。它被称为“臃肿接口”。ISP 建议将该接口拆分为两个或更多接口,以便一个类可以实现它所需的特定接口。
让我们使用以下接口来详细学习 ISP
public interface IStudentRepository
{
void AddStudent(Student std);
void EditStudent(Student std);
void DeleteStudent(Student std);
void AddCourse(Course cs);
void EditCourse(Course cs);
void DeleteCourse(Course cs);
bool SubscribeCourse(Course cs);
bool UnSubscribeCourse(Course cs);
IList<Student> GetAllStudents();
IList<Student> GetAllStudents(Course cs);
IList<Course> GetAllCourse();
IList<Course> GetAllCourses(Student std);
}
public class StudentRepository : IStudentRepository
{
public void AddCourse(Course cs)
{
//implementation code removed for better clarity
}
public void AddStudent(Student std)
{
//implementation code removed for better clarity
}
public void DeleteCourse(Course cs)
{
//implementation code removed for better clarity
}
public void DeleteStudent(Student std)
{
//implementation code removed for better clarity
}
public void EditCourse(Course cs)
{
//implementation code removed for better clarity
}
public void EditStudent(Student std)
{
//implementation code removed for better clarity
}
public IList<Course> GetAllCourse()
{
//implementation code removed for better clarity
}
public IList<Course> GetAllCourses(Student std)
{
//implementation code removed for better clarity
}
public IList<Student> GetAllStudents()
{
//implementation code removed for better clarity
}
public IList<Student> GetAllStudents(Course cs)
{
//implementation code removed for better clarity
}
public bool SubscribeCourse(Course cs)
{
//implementation code removed for better clarity
}
public bool UnSubscribeCourse(Course cs)
{
//implementation code removed for better clarity
}
}
上面的 IStudentRepository
接口包含 12 个用于不同目的的方法。StudentRepository
类实现了 IStudentRepository
接口。
现在,一段时间后,你发现 StudentRepository
类的所有实例都不会调用所有方法。有时它调用执行学生相关任务的方法,有时调用课程相关的方法。此外,StudentRepository
不遵循单一职责原则,因为如果学生相关和课程相关的业务逻辑发生变化,你可能需要修改其代码。
为了将 ISP 应用于上述问题,我们可以拆分我们的大接口 IStudentRepository
,并创建另一个包含所有课程相关方法的接口 ICourseRepository
,如下所示。
public interface IStudentRepository
{
void AddStudent(Student std);
void EditStudent(Student std);
void DeleteStudent(Student std);
bool SubscribeCourse(Course cs);
bool UnSubscribeCourse(Course cs);
IList<Student> GetAllStudents();
IList<Student> GetAllStudents(Course cs);
}
public interface ICourseRepository
{
void AddCourse(Course cs);
void EditCourse(Course cs);
void DeleteCourse(Course cs);
IList<Course> GetAllCourse();
IList<Course> GetAllCourses(Student std);
}
现在,我们可以创建两个具体的类来实现上述两个接口。这将自动支持 SRP 并增加内聚性。
ISP 不仅限于接口,它也可以用于抽象类或任何向客户端代码提供服务的类。
ISP 有助于实现里氏替换原则,增加内聚性,进而支持单一职责原则。
以下代码异味检测 ISP 违规:
- 当你有大型接口时。
- 当你在一个具体类中实现一个接口,其中一些方法没有任何实现代码或抛出
NotImplementedException
时。 - 当你只调用一个大型接口中的一小部分方法时。
ISP 违规的解决方案:
- 将大型接口拆分为更小的接口。
- 如果需要,继承多个小接口。
- 对第三方大型接口使用适配器设计模式,以便你的代码可以使用适配器。