#oop #interface #interface-segregation-principle
#ооп #интерфейс #Принцип разделения интерфейса
Вопрос:
У меня ситуация, когда мне нужно вызвать стороннюю службу для получения некоторой информации. Эти услуги могут отличаться для разных клиентов. У меня есть функция аутентификации в моем интерфейсе следующим образом.
interface IServiceProvider {
bool Authenticate(string username, string password);
}
class ABCServiceProvider : IserviceProvider
{
bool Authenticate(string username, string password) { // implementation}
}
class EFGServiceProvider : IserviceProvider
{
bool Authenticate(string username, string password) { // implementation}
}
и так далее… теперь я наткнулся на поставщика услуг (скажем, XYZServiceProvider), которому требуется некоторая дополнительная информация (идентификатор агента) для аутентификации. что-то вроде этого…
class XYZServiceProvider
{
bool Authenticate(string username, string password, int agentid) { // implementation}
}
Теперь, если я предоставлю другую функцию для аутентификации в своем интерфейсе с 3 параметрами и создам не реализованное исключение во всех классах, кроме XYZServiceProvider, не нарушит ли это принцип разделения интерфейса? У меня аналогичная ситуация и в другой части моего кода. Может кто-нибудь, пожалуйста, скажите мне, какой наилучший способ реализовать этот тип сценария? Я был бы действительно очень благодарен.
Ответ №1:
Вероятно, лучшим способом решить эту проблему было бы потребовать идентификатор агента в интерфейсе и просто игнорировать его в случаях ABC и DEF, где он им не нужен. Таким образом, класс-потребитель все равно не будет знать разницы.
На самом деле это принцип подстановки Лискова, который наиболее важен, если поставщики ABC, DEF и XYZ должны использоваться взаимозаменяемо; «Учитывая класс A, от которого зависит класс X, X должен иметь возможность использовать класс B, производный от A, не зная разницы».
Принцип разделения интерфейса в основном гласит, что интерфейс не должен содержать элементы, которые никому из его потребителей не нужны, потому что, если бы определение этих элементов изменилось, классы, которые даже не используют этот метод, пришлось бы перекомпилировать, потому что интерфейс, от которого они зависели, изменился. Хотя это имеет значение (вам нужно перекомпилировать всех пользователей IServiceProvider, если вы добавите перегрузку), вам все равно придется это сделать, если вы измените подпись Authenticate() , и более насущной проблемой с точки зрения обслуживания является то, что если вы добавили перегрузку Authenticate(), ваштеперь потребители должны знать, какую перегрузку им нужно использовать. Это требует, чтобы ваши классы-потребители знали разницу между реализациями общего интерфейса, нарушая LSP. Никогда не бывает проблем с предоставлением большего количества информации, чем требуется конкретному поставщику, но может возникнуть проблема с использованием XYZ из-за использования, которое предоставляет только два ввода. Чтобы избежать этих проблем, вы всегда будете использовать трехпараметрическую перегрузку, так зачем вообще использовать двухпараметрическую?
Теперь, если текущее использование IServiceProvider находится в областях, которые не имеют и не заботятся об идентификаторе агента, и поэтому было бы трудно начать его предоставлять, тогда я бы порекомендовал адаптер, к которому подключается конкретный поставщик XYZ, который реализует ваш текущий IServiceProvider и заставляет нового поставщика работать какстарые путем предоставления идентификатора агента каким-либо другим способом:
public class XYZAdapter: IServiceProvider
{
private readonly XYZServiceProvider xyzProvider;
public XYZAdapter(XYZServiceProvider provider)
{
xyzProvider = provider;
}
public void Authenticate(string username, string password)
{
xyzProvider.Authenticate(username, password, GetAgentId());
}
public int GetAgentId()
{
//Retrieve the proper agent Id. It can be provided from the class creator,
//retrieved from a known constant data source, or pulled from some factory
//method provided from this class's creator. Any way you slice it, consumers
//of this class cannot know that this information is needed.
}
}
Если это возможно, оно соответствует как LSP, так и ISP; интерфейс не должен меняться для поддержки LSP, что предотвращает сценарий (перекомпиляцию и перераспределение зависимостей), которого обычно пытается избежать ISP. Однако это увеличивает количество классов и заставляет новую функциональность в адаптере корректно получать необходимый идентификатор агента без необходимости предоставлять что-либо, о чем он не знал бы, через интерфейс IServiceProvider.
Комментарии:
1. Также определите новый интерфейс, такой как IAgentServiceProvider. Make XYZserviceProvider реализует IAgentServiceProvider. Теперь примените шаблон адаптера, в котором класс XYZServiceProvider является адаптируемым. Класс адаптера будет реализовывать IServiceProvider и содержать ссылку на IAgentServiceProvider