Как избежать чрезмерной перегрузки метода?

16

У нас довольно много мест в исходном коде нашего приложения, где один класс имеет много методов с одинаковыми именами и разными параметрами. Эти методы всегда имеют все параметры «предыдущего» метода плюс еще один.

Это результат долгой эволюции (устаревший код) и такого мышления (я считаю):

« Есть метод M, который выполняет функцию A. Мне нужно сделать A + B. Хорошо, я знаю ... Я добавлю новый параметр в M, создам новый метод для этого, переместлю код из M в новый метод с еще одним параметром, выполните там A + B и вызовите новый метод из M со значением по умолчанию для нового параметра. "

Вот пример (на Java-подобном языке):

class DocumentHome {

  (...)

  public Document createDocument(String name) {
    // just calls another method with default value of its parameter
    return createDocument(name, -1);
  }

  public Document createDocument(String name, int minPagesCount) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false);
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false, "");
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank, String title) {
    // here the real work gets done
    (...)
  }

  (...)
}

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

Вот несколько способов сделать это лучше:

  1. Введите объект параметра:

    class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    
  2. Установите параметры для DocumentHomeобъекта перед вызовомcreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. Разделите работу на разные методы и при необходимости вызывайте их:

      @In
      DocumentHome dh = null;
    
      Document newDocument = dh.createDocument();
      dh.changeName(newDocument, "name");
      dh.addFirstBlankPage(newDocument);
      dh.changeMinPagesCount(new Document, 10);
    

Мои вопросы:

  1. Является ли описанная проблема действительно проблемой?
  2. Что вы думаете о предлагаемых решениях? Какой из них вы бы предпочли (исходя из вашего опыта)?
  3. Можете ли вы придумать какое-либо другое решение?
Ytus
источник
1
На какой язык вы ориентируетесь или это просто sth generell?
Knerd
Никакого конкретного языка, просто общий. Не стесняйтесь показать, как функции на других языках могут помочь с этим.
Ytus
Как я уже сказал, у programmers.stackexchange.com/questions/235096/… C # и C ++ есть некоторые особенности.
Knerd
Совершенно очевидно, что этот вопрос относится ко всем языкам, поддерживающим такую ​​перегрузку методов.
Док Браун
1
@DocBrown хорошо, но не все языки поддерживают одинаковые альтернативы;)
Knerd

Ответы:

20

Может быть, попробовать шаблон строителя ? (примечание: довольно случайный результат Google :)

var document = new DocumentBuilder()
                   .FirstPageBlank()
                   .Name("doc1final(2).doc")
                   .MinimumNumberOfPages(4)
                   .Build();

Я не могу дать полное изложение того, почему я предпочитаю конструктор, а не опции, которые вы предоставляете, но вы обнаружили большую проблему с большим количеством кода. Если вы считаете, что для метода нужно более двух параметров, возможно, ваш код структурирован неправильно (а некоторые утверждают, что один!).

Проблема с объектом params заключается в том, что (если созданный вами объект не является каким-то реальным), вы просто поднимаете проблему на уровень и в итоге получаете кластер несвязанных параметров, образующих «объект».

Ваши другие попытки выглядят для меня как будто кто-то тянется к образцу строителя, но не совсем добирается :)

Фрум
источник
Спасибо. Ваш ответ может быть решением номер. 4 да. Я ищу больше ответов таким образом: «По моему опыту, это приводит к ... и может быть исправлено ..." или «Когда я вижу это в кодексе моего коллеги, я предлагаю ему ... вместо этого».
Ytus
Я не знаю ... мне кажется, что вы просто решаете проблему. Например, если я могу построить документ в разных частях приложения, лучше организовать и протестировать это, изолируя конструкцию этого документа в отдельном классе (например, use a DocumentoFactory). Располагая в builderразных местах, трудно контролировать будущие изменения в конструкции документа (например, добавить новое обязательное поле в документ, например) и добавить дополнительный шаблонный код в тесты, чтобы просто удовлетворить потребности сборщика документов в классах, которые используют построитель.
Дерик
1

Использование объекта параметра - хороший способ избежать (чрезмерной) перегрузки методов:

  • это очищает код
  • отделяет данные от функциональности
  • делает код более понятным

Я бы, однако, не пошел слишком далеко с этим.

Перегрузка здесь и там не плохая вещь. Он поддерживается языком программирования, поэтому используйте его в своих интересах.

Я не знал о строительном шаблоне, но несколько раз использовал его «случайно». То же самое и здесь: не переусердствуйте. Код в вашем примере выиграл бы от этого, но тратить много времени на его реализацию для каждого метода, который имеет один метод перегрузки, не очень эффективно.

Просто мои 2 цента.

обкрадывать
источник
0

Честно говоря, я не вижу большой проблемы с кодом. В C # и C ++ вы можете использовать необязательные параметры, которые будут альтернативой, но, насколько я знаю, Java не поддерживает такие параметры.

В C # вы можете сделать все перегрузки частными, и один метод с необязательными параметрами является общедоступным для вызова вещи.

Чтобы ответить на ваш вопрос, часть 2, я бы взял параметр объекта или даже словарь / HashMap.

Вот так:

class DokumentHome {

  public Document createDocument(Map<string, object> params) {
    if (params.containsKey("yourkey")) {
       // do that
    }
    // here the real work gets done
    (...)
  }
}

Как оговорка, я сначала программист на C # и JavaScript, а затем программист на Java.

Knerd
источник
4
Это решение, но я не думаю, что это хорошее решение (по крайней мере, не в контексте, где ожидается безопасность типов).
Док Браун
Это верно. Из-за подобных случаев мне нравятся перегруженные методы или необязательные параметры.
Knerd
2
Использование словаря в качестве параметра - это простой способ уменьшить количество видимых параметров для метода, но он скрывает фактические зависимости метода. Это заставляет вызывающего пользователя искать в другом месте, что именно должно быть в словаре, будь то в комментариях, других вызовах метода или самой реализации метода.
Майк Партридж
Используйте словарь только для действительно необязательных параметров, поэтому в базовых сценариях не нужно читать слишком много документации.
user949300
0

Я думаю, что это хороший кандидат на модель строителя. Шаблон Builder полезен, когда вы хотите создавать объекты одного типа, но с разными представлениями.

В вашем случае у меня был бы конструктор со следующими методами:

SetTitle (Document document) { ... }
SetFirstPageBlank (Document document) { ... }
SetMinimumPageCount (Document document) { ... }

Затем вы можете использовать конструктор следующим образом:

_builder.SetTitle(document);
_builder.SetFirstPageBlank(document);

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

CodeART
источник
0

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

Если вы просто создадите простой конструктор для создания документа и распространите этот код по различным частям (классам) приложения, вам будет сложно:

  • организовать : у вас будет много классов, строящих документ по-разному
  • поддерживать : любое изменение в экземпляре документа (например, добавление нового обязательного поля) приведет вас к операции на дробовике
  • test : если вы тестируете класс, который создает документ, вам нужно добавить шаблонный код только для того, чтобы удовлетворить реализацию документа.

Но это не ответ на вопрос ОП.

Альтернатива перегрузке

Некоторые альтернативы:

  • Переименуйте имя метода : если то же имя метода создает некоторую путаницу, попробуйте переименовать методы , чтобы создать значащее имя лучше , чем createDocument, как: createLicenseDriveDocument, createDocumentWithOptionalFieldsи т.д. Конечно, это может привести вас к гигантским именам методов, так что это не решение для всех случаев.
  • Используйте статические методы . Этот подход отчасти похож на первый вариант выше. Вы можете использовать осмысленные имена для каждого случая и экземпляр документа от статического метода на Document, как: Document.createLicenseDriveDocument().
  • Создайте общий интерфейс : вы можете создать единственный вызванный метод createDocument(InterfaceDocument interfaceDocument)и создать различные реализации для него InterfaceDocument. За пример: createDocument(new DocumentMinPagesCount("name")). Конечно, вам не нужна отдельная реализация для каждого случая, потому что вы можете создать более одного конструктора для каждой реализации, группируя некоторые поля, которые имеют смысл для этой реализации. Этот шаблон называется телескопическими конструкторами .

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

Dherik
источник