Ответы пользователя по тегу Паттерны проектирования
  • Часто ли вы используете интерфейсы?

    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: ''
          };
      
        }
      
        // ...
      
      }

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

    Ответ написан
    Комментировать