@Venesuella
BlackJack и ...

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

Здравствуйте! Разбираюсь с темой интерфейсов, задавая вопрос "для чего, зачем нужны интерфейсы", часто получал ответ для слабой связанности компонентов/заменяемости компонентов, для лучшего тестирования. И вопрос такой часто ли вы окружаете интерфейсами свои слои приложения, или еще что то? Часто ли у вас было что ваши репозитории окруженные интерфейсами используют MSSQL, и есть еще такие же репозитории но уже использующие к примеру MongoDB, и где нито вы подменяете одно другим? Часто ли у вас было что у вас есть 2 разных слоя сервисов, окруженных интерфейсами, и опять же вы один подменяете другим? Такое реально возможно, если вряд ли, либо вероятность мала, получается интерфейсы в основном используются для тестирования, чтобы можно было использовать моки? Так ли, разъясните, разжуйте, приведите реальные примеры, очень хочу в теме разобраться
  • Вопрос задан
  • 917 просмотров
Пригласить эксперта
Ответы на вопрос 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, два из которых как про интерфейсы - принцип инверсии зависимостей и принцип разделения интерфейсов. Если кратко про инверсию - потребители должны зависеть от абстракции, а не от реализации, в том числе на уровне сборок. Чтобы сократить и упорядочить зависимости между сборками, как правило выделяют сборку с интерфейсами и контрактами, которую используют сборки с реализацией, сборки потребители и сборки с тестами.


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

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

Войти через центр авторизации
Похожие вопросы