@Venesuella
BlackJack и ...

Часто ли вы используете интерфейсы?

Здравствуйте! Разбираюсь с темой интерфейсов, задавая вопрос "для чего, зачем нужны интерфейсы", часто получал ответ для слабой связанности компонентов/заменяемости компонентов, для лучшего тестирования. И вопрос такой часто ли вы окружаете интерфейсами свои слои приложения, или еще что то? Часто ли у вас было что ваши репозитории окруженные интерфейсами используют MSSQL, и есть еще такие же репозитории но уже использующие к примеру MongoDB, и где нито вы подменяете одно другим? Часто ли у вас было что у вас есть 2 разных слоя сервисов, окруженных интерфейсами, и опять же вы один подменяете другим? Такое реально возможно, если вряд ли, либо вероятность мала, получается интерфейсы в основном используются для тестирования, чтобы можно было использовать моки? Так ли, разъясните, разжуйте, приведите реальные примеры, очень хочу в теме разобраться
  • Вопрос задан
  • 923 просмотра
Пригласить эксперта
Ответы на вопрос 7
@Beltoev
Живу в своё удовольствие
Самый простой пример, который приходит на ум: представьте, что вы хотите получать оповещения об ошибках на сайте по email/смс/чему-нибудь ещё.

С интерфейсами ваше решение будет выглядеть как-то так:
// Собственно, сам интерфейс оповещений
public interface INotification
{
    void Notify(string text);
}

public class EmailNotification : INotification
{
    public void Notify(string text)
    {
		// код по отправке почты
    }
}

public class SmsNotification : INotification
{
    public void Notify(string text)
    {
		// код по отправке смс
    }
}

// ... Еще какие-нибудь классы оповещений


// В каком-нибудь классе, где может появиться ошибка
public class MaybeErrorClass
{
    private INotification _notification;

    public MaybeErrorClass(INotification notification)
    {
		// Класс не должен знать, по какому каналу оповещать об ошибках.
		// Он работает с абстракцией
        this._notification = notification;
    }

	// Очень простой пример метода, в котором ожидаем генерацию ошибки
	public void DoSomething()
	{
		try {
			// какой-то блок, в котором можем получить ошибку
		}
		catch (Exception e)
		{
			this._notification.Notify("А у нас тут ошибка!");
		}
	}
}


Теперь можно создавать экземпляры этого класса, передавая ему желаемый тип оповещения:
var maybeErrorEmail = new MaybeErrorClass(new EmailNotification());
var maybeErrorSms = new MaybeErrorClass(new SmsNotification());


Теперь вопрос на засыпку: как бы вы решили подобную задачу без интерфейсов?

На ум приходят только абстрактные классы (кстати, интерфейс - это тоже абстрактный класс), но их лучше использовать только в случае, если у производных классов есть какая-то общая логика, которую не хотелось бы дублировать.
Ответ написан
Nipheris
@Nipheris Куратор тега C#
Часто ли вы используете интерфейсы?

Посмотрим, что у нас в стандартной библиотеке:

Со всеми этими интерфейсами дотнет разработчик сталкивается каждый день. Либо пользуется ими, либо реализует их. Например, тот же yield return работает благодаря IEnumerable. Весь LINQ основан на IEnumerable и IQueryable.

Часто ли у вас было что ваши репозитории окруженные интерфейсами используют MSSQL, и есть еще такие же репозитории но уже использующие к примеру MongoDB, и где нито вы подменяете одно другим?

Да, часто. Например, архитектура ADO.NET позволяет написать приложение так, что оно вообще не будет зависеть от используемого драйвера БД, и конкретный ado.net драйвер можно указать в конфиг-файле приложения. Т.е. конкретную БД может выбрать ПОЛЬЗОВАТЕЛЬ, а не разработчик. Более того, если под какую-то БД драйвера еще нет, а позже он появится, то его можно будет задействовать без перекомпиляции приложения. Только потому, что используются интерфейсы.
Ответ написан
Комментировать
AlekseyNemiro
@AlekseyNemiro
full-stack developer
Интерфейсы использую, если это необходимо :-) Как я понимаю, что это необходимо, сказать сложно. Все зависит от задачи. Если рассматривать вопрос с позиции создания интерфейсов, то иногда это может быть очевидно, а иногда приходится подумать, стоит использовать интерфейсы или нет. Пихать их где попало - плохая идея.

Готовые интерфейсы, да, часто используются. Самым популярным в .NET наверное будет IDisposable :-)

Из общедоступных практических примеров использования собственных интерфейсов:

  • Простой интерфейс, описывающий всего один метод.

    interface ILoginForm
    {
    
      void WebDocumentLoaded(System.Windows.Forms.WebBrowser webBrowser, Uri url);
    
    }


    В проекте 100500 форм. Для некоторых форм может потребоваться индивидуальная обработка результатов, как здесь. А для других - нет. Все формы наследуются от базового класса, в котором я могу проверить, реализует текущий экземпляр интерфейс с индивидуальным обработчиком результатов или нет, и вызвать его.

    if (typeof(ILoginForm).IsAssignableFrom(this.GetType()))
    {
      this.CanLogin = false;
      Debug.WriteLine("ILoginForm", "LoginForm");
      ((ILoginForm)this).WebDocumentLoaded(this.webBrowser1, e.Url);
    }


  • Вот другой пример интерфейса.

    export interface ILocalization {
    
      Loading: string;
    
      LoadingFileContents: string;
    
      // ...
    }

    Файлы с ресурсами локализации реализуют этот интерфейс.

    export class RU implements ILocalization {
    
      public Loading: string = 'Загрузка...';
    
      public LoadingFileContents: string = 'Получение содержимого файла...';
    
      // ...
    }

    Это просто упрощает работу с кодом и больше ничего.

    6d84933efd7246e6adbeeed114ef8bbe.png

    Можно было бы использовать обычные текстовые ключи, но тогда программистам пришлось бы постоянно смотреть, какие есть ключи, делать много лишних движений и вероятность ошибок возрастает.


  • Еще пример интерфейса для вспомогательных классов работы с базами данных. Он просто описывает, каким должен быть класс.

    interface IDBClient
    {
        
      public function ExecuteNonQuery();
      public function ExecuteScalar();
      public function GetRow();
      // ...
    
    }

    А толку от этого никакого.

    Интерфейсы для слоев взаимодействия с БД я делаю частенько, с замахом на замену СУБД, но сделать это красиво в любом случае не получится, все равно придется вносить корректировки при изменении источника данных, так что практической пользы от таких интерфейсов мало, но иногда она есть, только для других целей.

  • А вот тут я реализовал множество готовых интерфейсов, чтобы получить нужные свойства и поведение класса. Например, интерфейс IConvertible позволяет адекватно реагировать на Convert.To*. Интерфейс ISerializable позволяет описать правила сериализации экземпляра класса. И т.п.

    // реализация метода ToInt32
    public int ToInt32(IFormatProvider provider)
    {
      // если пусто, возвращаем ноль
      if (!this.HasValue) { return 0; }
      // если что-то есть, извлекаем числа и пробуем вернуть int32
      return Convert.ToInt32(OAuthUtility.GetNumber(this.Data));
    }


  • Вот пример, где можно было использовать интерфейсы, но я принял решение в пользу базовых классов (на раннем этапе). Некоторые поставщики OAuth позволяют обновлять маркер доступа и/или отзывать его, но не все. В базовом классе нижнего уровня для определениях этих особенностей я сделал два свойства, причем даже не абстрактных.

    public bool SupportRevokeToken { get; protected set; }
    public bool SupportRefreshToken { get; protected set; }

    Клиенты, которым нужны эти свойства, задают их в конструкторе, выглядит это так.

    public GoogleClient(string clientId, string clientSecret) : base(/*...*/)
    {
      // ...
      base.SupportRevokeToken = true;
      base.SupportRefreshToken = true;
    }

    Не очень красиво, но ошибкой это назвать нельзя. Если бы я использовал интерфейсы, то для отзыва и обновления маркера мне бы пришлось делать два разных интерфейса, в каждом по одному bool свойству, либо пустых (но с явно определенным свойством для других программистов все было бы очевидней; это тоже спорный вопрос). Можно было пойти дальше и измельчить на интерфейсы другие особенности, но это было бы плохим решением, т.к. программистам пришлось бы указывать портянку интерфейсов, а потом писать много кода. С базовыми классами код писать не нужно, даже не нужно вникать в то, как это работает; в большинстве случаев достаточно просто выполнить простую конфигурацию дочернего класса в конструкторе. Но я не могу утверждать на 100%, что это лучшее решение для данного случая.

  • Пример, где ReactJS и TypeScript заставляют клепать интерфейсы и я этому совсем не рад.

    export interface ILoginState {
    
      Username?: string;
      Password?: string;
    
      // ...
    
    }
    
    export default class Index extends Page<any, ILoginState> {
    
      constructor() {
    
        this.state = {
          Username: '',
          Password: ''
        };
    
      }
    
      // ...
    
    }

    Можно забить и использовать анонимные типы, но для порядка и удобства работы с кодом придется открывать собственный завод по производству интерфейсов :-)

Ответ написан
Комментировать
Польза интерфейсов настолько очевидна, даже не знаю, с чего начать.
Читайте про ООП, паттерны, тестирование.
Ответ написан
AlexXYZ
@AlexXYZ
O Keep Clear O
У каждого своё понимание интерфейса и в зависимости от этого и пишутся программы с использованием тех или иных шаблонов. Обычно для задачи выбирается стек технологий и если выбран удачно, то программа будет жить долго и счастливо. Ну, а если нет, то... не повезло.
Ответ написан
Комментировать
Milk92
@Milk92
.NET
Я отталкиваюсь от понимания полиморфизма.. то есть многообразия форм объекта (интерфейсной ссылки). Об этом написано в любой книге по языку C# и по любому другому языку с парадигмой ООП..
Тут самому нужно понимать когда его использовать..
Например если у вас не один источник а два.. то нужно разработать API которое дает методы, работы с данными независимо от источника..
То есть:
public interface IDataAccess{
void Connection();
void BeginTransaction();

}
public class MySqlDal:IDataAccess{
      public void Contection(){
         //TODO implement method!
    }
     public void BeginTransaction(){
       //TODO implement method!
    }
}
public class MsSqlDal:IDataAccess{
      public void Contection(){
         //TODO implement method!
    }
     public void BeginTransaction(){
       //TODO implement method!
    }
}

А дальше уже использовать так:
public IDataAccess mySqlDal = new MySqlDal();
public IDataAccess msSqlDal = new MsSqlDal();

понимаю, что грубый пример но как то вот так.
Ответ написан
Комментировать
RyzhovAlexandr
@RyzhovAlexandr
люблю .NET, интересуюсь также Java, BigData
Рискую повториться, но позволю описать свое видение.
Можно выделить несколько случаев, когда удобно использовать интерфейсы:
  • Абстракция, предполагающая несколько одновременно используемых реализаций. Например IEnumerable или как пример с паттерном "Стратегия" в первом комментарии.
  • Несколько взаимозаменяемых реализаций одной абстракции. Например логгер, можно логировать в файл, в табличку на форме, в БД.
  • Замена для тестирования. Хотя при желании это можно сделать и с виртуальными методами, но как правило, с интерфейсами удобнее, с учетом множественной реализации нескольких интерфейсов, но наследование только от одного базового класса, да и базовая реализация как правило тянет за собой клубок зависимостей.
  • Для следования принципу инверсии зависимостей. Очень рекомендую ознакомиться с принципами SOLID, два из которых как про интерфейсы - принцип инверсии зависимостей и принцип разделения интерфейсов. Если кратко про инверсию - потребители должны зависеть от абстракции, а не от реализации, в том числе на уровне сборок. Чтобы сократить и упорядочить зависимости между сборками, как правило выделяют сборку с интерфейсами и контрактами, которую используют сборки с реализацией, сборки потребители и сборки с тестами.


По поводу когда следует вводить интерфейс - обычно я ввожу интерфейс, если вижу в этом смысл по описанным выше причинам, если смысла нет, то и вводить не тороплюсь, ведь его всегда можно выделить, главное чтобы была понятная для вас причина.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы