Реализация интерфейса (принцип разделения интерфейса)

У меня есть ситуация, когда мне нужно позвонить в стороннюю службу, чтобы получить какую-то информацию. Эти услуги могут быть разными для разных клиентов. У меня есть функция аутентификации в моем интерфейсе следующим образом.

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), который нуждается в некоторой дополнительной информации (agentid) для аутентификации. что-то вроде этого...

class XYZServiceProvider
{
 bool Authenticate(string username, string password, int agentid) { // implementation}
}

Теперь, если я предоставлю еще одну функцию для Authenticate в моем интерфейсе с 3 параметрами и выброшу не реализованное исключение во всех классах, кроме XYZServiceProvider, не будет ли это нарушать принцип сегрегации интерфейса? У меня похожая ситуация в некоторых других частях кода. Может кто-нибудь сказать мне, каков наилучший способ реализовать этот тип сценария? Я был бы очень благодарен.

1 ответ

Решение

Лучшим способом решения этой проблемы, вероятно, было бы использование agentId в интерфейсе и просто игнорирование его в случаях ABC и DEF, где они не нужны. Таким образом, потребительский класс все равно не узнает разницу.

На самом деле, это принцип замены Лискова, который наиболее важен, если поставщики ABC, DEF и XYZ должны использоваться взаимозаменяемо; "Учитывая класс A, от которого зависит класс X, X должен иметь возможность использовать класс B, производный от A, не зная различий".

Принцип сегрегации интерфейса в основном гласит, что интерфейс не должен содержать членов, которые никому из его потребителей не нужны, потому что, если определение этих членов должно было измениться, классы, которые даже не используют этот метод, должны были бы перекомпилироваться, потому что интерфейс они зависели от того, что изменилось. Хотя это актуально (вам придется перекомпилировать всех потребителей IServiceProvider, если вы добавите перегрузку), вам все равно придется это делать, если вы измените подпись Authenticate(), и с точки зрения обслуживания более насущной проблемой является то, что если вы добавили перегрузку Authenticate(), теперь ваши потребители должны знать, какую перегрузку им нужно использовать. Это требует, чтобы ваши потребляющие классы знали разницу между реализациями общего интерфейса, нарушая LSP. Это никогда не проблема, предоставляя больше информации, чем требуется конкретному поставщику, но будет проблема с использованием XYZ из использования, которое обеспечивает только два входа. Чтобы избежать этих проблем, вы всегда должны использовать трехпараметрическую перегрузку, так зачем вообще использовать двухпараметрическую?

Теперь, если текущие применения IServiceProvider находятся в областях, которые не имеют и не заботятся о agentId, и, следовательно, было бы трудно начать предоставлять его, то я бы порекомендовал адаптер, в который подключается конкретный поставщик XYZ, который реализует ваш текущий IServiceProvider, и заставляет нового поставщика работать как старые, предоставляя agentId другими способами:

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.

Другие вопросы по тегам