Слишком много методов у интерфейса - зло

Я увидел тут код, с не очень приятной для мена архитектурой, где бизнес логика выполнена в виде больших интерфейсов. В каждом интерфейсе было по несколько десятков методов. Я с таким встречался раньше и обычно за подобным интерфейсом скрывался класс фасад, который скрывает вызов сложных вещей.

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

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

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

Короче, недавно одному программисту явно понадобилось заменить один функционал, который в реальности реализован в виде двух методов большого интерфейса. На мой взгляд они логически должны были жить в отдельном интерфейсе, но их засунули в один большой. Если бы они были реально реализованы отдельно, то можно было бы создать новую реализацию и подсунуть ее. Но в этом случае такое было бы невозможно, поэтому вместо создания новой реализации интерфейса, в него просто добавили набор новых методов CalcSomethingMethodEnhanced и GetMeЕhatEnhanced.

Как только я вижу методы с приставками Enhanced или с цифрами – это уже указатель на плохую архитектуру.

Представьте себе архитектора автомобиля, который весь автомобиль делает с помощью одного единственного интерфейса:

ICar {
   public Start();
   public Stop();
   public TurnOnLight();
   public TurnoffLight();
   public TurnLeft();
   public TurnRight();
   . . . 
}

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

Но теперь посмотрим на возможную реализацию:

Car : iCar {
   public Start() {
     if (this.НеРаботает == true) {
        while(this.КлючВПозицииСтарта == true) {
            if (this.ПопробоватьЗапустить ()) {
               break;
            }
        }
        If (this.Работает == true) {
            this.ПроверитьУровеньМасла()
        }
     }
   }
   . . . 
}

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

CarWithFogLight: ICar

И тут вы сталкиваетесь с проблемой – нужно снова реализовывать эту гребанную логику старта машины, хотя она абсолютно идентична. Да ну на, скажет любой программист и просто добавить метод включения противотуманок в ICar, и расширит реализацию метода Car с помощью этого метода, где будет проверка – а есть ли у машины противотуманки? Если есть, то включить разрешить включение.

Более опытный программист скажет, а почему не создать интерфейс ICarWithFogLight, а класс CarWithFogLight просто должен будет наследовать базовый функционал Car. Этот вариант чуть лучше. Это всегда лучше, чем создавать методы типа Enhanced. Но почему-то в этом проекте наследования вообще не используется. Да, сейчас лучше использовать композицию, но это не значит, что наследование использовать нельзя. Там, где лучше подходит наследование, его нужно использовать.

Даже такая реализация на мой взгляд не очень:

Car : iCar {
   IEngine engine;
   public Start() {
     if (engine.НеРаботает == true) {
        while(engine.КлючВПозицииСтарта == true) {
            if (engine.ПопробоватьЗапустить ()) {
               break;
            }
        }
        If (engine.Работает == true) {
            engine.ПроверитьУровеньМасла()
        }
     }
   }
   . . . 
}

Тут слишком много логики, которой просто не должно быть в подобном классе.

Если в реализации слишком много логики типа "если", то это явный признак того, что просто должно быть два разных класса и что-то не так с архитектурой. Если вы создали интерфейс типа ICar, как показано выше, то реализация должна быть простым фасадом или абстрактным классом, который легко потом можно расширять, наследовать и улучшать.

Car : iCar {
   IEngine engine;
   IElectricSystem electricSystem;
   public Start() {
      engine.Start()
   }
   public Stop(){
     engine.Stop();
   }
   public TurnOnLight() {
      electricSystem.TurnOnLight();
   }
   public TurnoffLight() {
      electricSystem.TurnOffLight();
   }
}

Таким образом мы скрыли все сложности работы машины, что там есть двигатель, электрическая система, что там есть коробка передач и т.д. Конечному пользователю достаточно только знать, что у машины можно включить свет и есть такой метод – все. Теперь наследовать и делать реализацию такого кода на много проще. Если вам нужно изменить, как работает электрическая система, нет необходимости изменять весь класс Car, нужно создать новую реализацию интерфейса IElectricSystem и все.

Теперь у нас есть класс Car, который скрывает все сложности работы машины. Хотя я даже такой подход считаю некрасивым, но забьем на это.



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

Паника, что-то случилось!!! Ничего не найдено в комментариях. Срочно нужно что-то добавить, чтобы это место не оставалось пустым.

Добавить Комментарий

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне