目前的情况
当前设置违反了接口隔离原则(SOLID中的I)。
参考
根据Wikipedia的说法,接口隔离原则(ISP)规定,不应强迫任何客户端依赖其不使用的方法。接口隔离原则是由Robert Martin在1990年代中期制定的。
换句话说,如果这是您的界面:
public interface IUserBackend
{
User getUser(int uid);
User createUser(int uid);
void deleteUser(int uid);
void setPassword(int uid, string password);
}
然后,实现此接口的每个类都必须利用该接口列出的每个方法。没有例外。
试想一下是否有一种通用方法:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
backendService.deleteUser(user.Uid);
}
如果要真正做到这一点,以使实际上只有一些实现类能够删除用户,则此方法有时会在您的面前夸张起来(或什么也不做)。那不是一个好的设计。
您建议的解决方案
我已经看到了一个解决方案,其中IUserInterface具有一个ImplementedActions方法,该方法返回一个整数,该整数是按位与操作与所请求的操作进行“与”运算的结果。
您本质上想要做的是:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
if(backendService.canDeleteUser())
backendService.deleteUser(user.Uid);
}
我忽略了我们如何精确地确定给定的类是否能够删除用户。不管它是布尔值还是位标志,都无所谓。一切都归结为一个二进制答案:可以删除用户,是还是否?
这样可以解决问题,对吗?好吧,从技术上讲,确实如此。但是现在,您违反了Liskov替代原则(SOLID中的L)。
放弃了相当复杂的Wikipedia解释,我在StackOverflow上找到了一个不错的示例。注意“坏”示例:
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
我认为您在这里看到了相似之处。这是应该处理的方法抽象对象(IDuck
,IUserBackend
),但由于被盗用一流的设计,它被强制为第一把手 具体实现(ElectricDuck
,确保它不是一个IUserBackend
类不能删除用户)。
这违背了开发抽象方法的目的。
注意:此处的示例比您的案例更容易解决。对于该示例,只需在方法内部ElectricDuck
启用转弯即可。两只鸭子仍然可以游泳,因此功能结果相同。Swim()
您可能想要做类似的事情。不要。您不仅可以假装删除用户,而且实际上有一个空的方法主体。尽管从技术角度来看这确实可行,但是它使得无法知道您的实现类在被要求执行某项操作时是否会真正执行某项操作。这是无法维护代码的温床。
我建议的解决方案
但是您说过,实现类仅可能处理其中一些方法(并且是正确的)。
举个例子,假设这些方法的每种可能组合都有一个实现它的类。它涵盖了我们所有的基地。
解决方案是拆分接口。
public interface IGetUserService
{
User getUser(int uid);
}
public interface ICreateUserService
{
User createUser(int uid);
}
public interface IDeleteUserService
{
void deleteUser(int uid);
}
public interface ISetPasswordService
{
void setPassword(int uid, string password);
}
请注意,您可能已经在我的答案的开头看到了这一点。该接口分离原则的名字已经揭示了这一原则的目的是让你隔离接口到足够的程度。
这使您可以根据需要混合和匹配接口:
public class UserRetrievalService
: IGetUserService, ICreateUserService
{
//getUser and createUser methods implemented here
}
public class UserDeleteService
: IDeleteUserService
{
//deleteUser method implemented here
}
public class DoesEverythingService
: IGetUserService, ICreateUserService, IDeleteUserService, ISetPasswordService
{
//All methods implemented here
}
每个类都可以决定自己想做的事情,而不必每次都破坏接口的约定。
这也意味着我们不需要检查某个类是否能够删除用户。每个实现该IDeleteUserService
接口的类都可以删除用户= 不违反Liskov替换原理。
public void HaveUserDeleted(IDeleteUserService backendService, User user)
{
backendService.deleteUser(user.Uid); //guaranteed to work
}
如果有人试图传递未实现的对象IDeleteUserService
,则程序将拒绝编译。这就是为什么我们喜欢具有类型安全性的原因。
HaveUserDeleted(new DoesEverythingService()); // No problem.
HaveUserDeleted(new UserDeleteService()); // No problem.
HaveUserDeleted(new UserRetrievalService()); // COMPILE ERROR
脚注
我以极端为例,将接口分成尽可能小的块。但是,如果您的情况有所不同,则可以摆脱更大的障碍。
例如,如果每个可以创建用户的服务始终能够删除用户(反之亦然),则可以将这些方法保留为单个接口的一部分:
public interface IManageUserService
{
User createUser(int uid);
void deleteUser(int uid);
}
这样做不是分离较小的块,这没有技术优势。但这将使开发稍微容易些,因为它需要更少的电镀。
IUserBackend
不应包含该deleteUser
方法。那应该是IUserDeleteBackend
(或任何您想称呼它的)一部分。需要删除用户的IUserDeleteBackend
代码将带有的参数,不需要该功能的代码将使用IUserBackend
并且不会对未实现的方法造成任何麻烦。