Как эффективно распределять функции между классами, не нарушая принцип подстановки Лискова

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

public class MainFunction {
  public void main(String option){
     if (option.equals("vanilla mode")){
        this.firstFunction();
     }else{
        this.differentVersionOfFirstFunction();
     }
     this.secondFunction();
  }

  public void firstFunction(){
    //First functions code
  }

  public void secondFunction(){
     //Second functions code
  }

  public void differentVersionOfFirstFunction(){
     // different code from First functions code that produces the same type of end result using a slightly different method
  }
}

Это постепенно становилось все более и более сложным, поскольку добавлялись различные опции, а код становился все более запутанным.

Чтобы решить эту проблему, я изначально планировал создать объект Parent, который затем мог бы иметь дочерние элементы с тонкими вариациями методов Parents, когда это необходимо. Проблема в том, что я понимаю, что это нарушит принцип замещения Лискова, и действительно, у меня могут быть дети, которые должны использовать тот же метод, которого может не быть у их родителя.

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

public class MainFunction {
  public void main1(){
    this.firstFunction();
    this.secondFunction();
  }

  public void main2(){
    this.differentVersionOfFirstFunction();
    this.secondFunction();
  }

  public void firstFunction(){
    //First functions code
  }

  public void secondFunction(){
    //Second functions code
  }

  public void differentVersionOfFirstFunction(){
    // different code from First functions code that produces the same type of end result using a slightly different method
  }
}

Я полагаю, что мог бы создать отдельный класс Utility для хранения всех моих различных функций, но я не был уверен, есть ли более элегантное решение?


person Mart    schedule 22.09.2016    source источник


Ответы (2)


Я не вижу, как ваши примеры нарушают принцип подстановки Лисков. Однако я вижу, что они, вероятно, нарушают принцип открытости/закрытости, а это означает, что каждый раз, когда вам нужно изменить свою программу, вы редактируете какой-то MainFunction.java файл и имеете шанс повлиять на все возможные сценарии запуска программы. программа. Лучшее решение включает в себя множество несвязанных компонентов, чтобы, когда вам нужно что-то изменить, вы изменяли только крошечную часть, которая вряд ли повлияет на все сценарии, с которыми может столкнуться программа. Вот что такое Принцип единой ответственности.

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

  1. Создайте интерфейс MainFunction методом void main() без каких-либо опций.
  2. Создайте абстрактный класс стратегии, такой как AbstractMainFunction, с элементом public abstract void main() без каких-либо опций. Этот класс будет реализовывать интерфейс MainFunction.
  3. Создайте отдельные реализации AbstractMainFunction по мере необходимости, например. VanillaModeMainFunction и DifferentMainFunction. Возможно, вам будет полезно хранить некоторый общий код в файле AbstractMainFunction.
  4. Создайте класс переключателя стратегии, например. MainFunctionService. У него будет метод public void main(String option), как в вашем первом примере, и, вероятно, будет такой оператор switch:

    MainFunction strategy = defaultFunction;
    switch(option) {
        case "vanilla":
             strategy = vanillaFunction;
             break;
        case "different":
             strategy = differentFunction;
             break;
    }
    strategy.main();
    

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

person Andrew Sklyarevsky    schedule 22.09.2016
comment
Не нарушает ли этот класс переключателей стратегий также принцип открытости/закрытости? - person jaco0646; 22.09.2016
comment
@ jaco0646, отличный вопрос, и ответ, наверное, зависит :-). Если опций слишком много и формат опций сложен, то да, вероятно, было бы лучше встроить функцию парсинга опций в сами стратегии и применить шаблон цепочка ответственности вместо шаблона стратегия. Однако, если логика относительно проста, на мой взгляд, было бы совершенно нормально, если бы точка входа программы имела оператор switch, и она соответствовала бы принципу единой ответственности — только одна причина для изменения, т. е. когда новая опция приходит в. - person Andrew Sklyarevsky; 22.09.2016
comment
Спасибо, у меня есть рабочая демонстрация этого, теперь нужно передать весь мой код! - person Mart; 22.09.2016
comment
Я реализовал это, хотя я не уверен, правильно ли я это сделал. Когда я делаю версию основной функции ванильного режима, скажем, с другим количеством обязательных аргументов для ключевой функции, тогда новая функция должна быть создана в child и все классы, от которых он наследуется, но эта новая функция будет бессмысленной для других дочерних элементов, которым не нужен дополнительный аргумент? - person Mart; 23.09.2016
comment
Если функции настолько различны, что различаются даже их аргументы, вызовите метод main для стратегий напрямую, например. case "a": strategy1.main(a, b, c); break; case "b": streategy2.main(x, y, z);. Рассмотрите также идею иметь класс Context, который вы будете передавать в качестве единственного аргумента любой стратегии. Затем стратегия сама извлечет необходимые данные из Context, или Context будет иметь некоторые общие методы для разбора аргументов. - person Andrew Sklyarevsky; 23.09.2016

Может быть, вы можете попробовать использовать шаблон стратегии, внедрив в свою MainFunction объект Strategy, который вам нужно использовать каждый раз. Посмотрите здесь

person Raffaele    schedule 22.09.2016
comment
Спасибо, это похоже на правильный путь, но я все еще обеспокоен тем, что может быть некоторое дублирование кода, когда у меня есть две очень похожие реализации, я попробую и посмотрю, как это работает. - person Mart; 22.09.2016
comment
Если вас беспокоит дублирование кода, ознакомьтесь с шаблоном template method, возможно, это поможет. - person Andrew Sklyarevsky; 22.09.2016