Jak uniknąć nadmiernego przeciążenia metody?

16

Mamy całkiem sporo miejsc w kodzie źródłowym naszej aplikacji, gdzie jedna klasa ma wiele metod o tych samych nazwach i różnych parametrach. Te metody zawsze mają wszystkie parametry metody „poprzedniej” plus jeszcze jedną.

Jest to wynik długiej ewolucji (starszego kodu) i tego myślenia (wierzę):

Istnieje metoda M, która wykonuje czynności A. Muszę wykonać A + B. OK, wiem… dodam nowy parametr do M, stworzę nową metodę, przeniesię kod z M do nowej metody z jeszcze jednym parametrem wykonaj tam A + B i wywołaj nową metodę z M z domyślną wartością nowego parametru.

Oto przykład (w języku 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
    (...)
  }

  (...)
}

Czuję, że to źle. Nie tylko dlatego, że nie możemy wiecznie dodawać nowych parametrów takich jak ten, ale kod jest trudny do rozszerzenia / zmiany z powodu wszystkich zależności między metodami.

Oto kilka sposobów, jak to zrobić lepiej:

  1. Wprowadź obiekt parametru:

    class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    
  2. Ustaw parametry DocumentHomeobiektu, zanim zadzwonimycreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. Podziel pracę na różne metody i nazwij je w razie potrzeby:

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

Moje pytania:

  1. Czy opisany problem jest naprawdę problemem?
  2. Co sądzisz o proponowanych rozwiązaniach? Który wolisz (na podstawie twojego doświadczenia)?
  3. Czy możesz wymyślić inne rozwiązanie?
Ytus
źródło
1
Na jaki język celujesz, czy jest to po prostu generell?
Knerd
Brak konkretnego języka, tylko ogólny. Pokaż, jak funkcje w innych językach mogą w tym pomóc.
Ytus
tak jak powiedziałem tutaj programmers.stackexchange.com/questions/235096/... C # i C ++ mają pewne funkcje.
Knerd
Jest całkiem jasne, że to pytanie dotyczy każdego języka, który obsługuje takie przeciążenie metod.
Doc Brown,
1
@DocBrown ok, ale nie każdy język obsługuje te same alternatywy;)
Knerd

Odpowiedzi:

20

Może spróbuj wzoru konstruktora ? (uwaga: dość losowy wynik Google :)

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

Nie mogę podać pełnego wyjaśnienia, dlaczego wolę konstruktora niż podane opcje, ale zauważyłeś duży problem z dużą ilością kodu. Jeśli uważasz, że potrzebujesz więcej niż dwóch parametrów do metody, prawdopodobnie masz nieprawidłową strukturę kodu (a niektórzy twierdzą, że jeden!).

Problem z obiektem params polega na tym, że (chyba że obiekt, który tworzysz, jest w jakiś sposób rzeczywisty), po prostu przesuwasz problem na wyższy poziom i otrzymujesz klaster niepowiązanych parametrów tworzących „obiekt”.

Inne twoje próby wyglądają jak ktoś sięgający po wzorzec konstruktora, ale nie do końca do niego docierający :)

Froome
źródło
Dziękuję Ci. Twoja odpowiedź może brzmieć numer num. 4 tak. Szukam więcej odpowiedzi w ten sposób: „Z mojego doświadczenia wynika, że ​​prowadzi to do ... i można to naprawić ...”. lub „Kiedy widzę to w kodzie mojego kolegi, sugeruję mu… zamiast tego”.
Ytus
Nie wiem ... wydaje mi się, że po prostu przenosisz problem. Na przykład, jeśli mogę zbudować dokument na różnych częściach aplikacji, lepiej zorganizować i przetestować to izolowanie konstrukcji tego dokumentu na oddzielnej klasie (np. Użyj a DocumentoFactory). Mając builderróżne miejsca, trudno jest kontrolować przyszłe zmiany w konstrukcji dokumentu (na przykład dodać nowe obowiązkowe pole do dokumentu, na przykład) i dodać dodatkowy kod płyty testowej w testach, aby spełnić wymagania konstruktora dokumentów w klasach, które używają konstruktora.
Dherik
1

Użycie obiektu parametru jest dobrym sposobem na uniknięcie (nadmiernego) przeciążenia metod:

  • czyści kod
  • oddziela dane od funkcjonalności
  • sprawia, że ​​kod jest łatwiejszy w utrzymaniu

Nie posunąłbym się jednak za daleko.

Przeciążenie tutaj i nic złego. Jest obsługiwany przez język programowania, więc użyj go na swoją korzyść.

Nie byłem świadomy wzorca budowniczego, ale kilka razy użyłem go „przypadkowo”. To samo dotyczy tutaj: nie przesadzaj. Kod w twoim przykładzie skorzystałby na tym, ale spędzanie dużo czasu na implementacji go dla każdej metody, która ma jedną metodę przeciążenia, nie jest zbyt wydajne.

Tylko moje 2 centy.

Obrabować
źródło
0

Naprawdę nie widzę dużego problemu z kodem. W C # i C ++ można użyć opcjonalnych parametrów, co byłoby alternatywą, ale o ile wiem, Java nie obsługuje tego rodzaju parametrów.

W języku C # można ustawić wszystkie przeciążenia jako prywatne, a jedna metoda z opcjonalnymi parametrami jest publiczna, aby wywołać takie operacje.

Aby odpowiedzieć na twoje pytanie część 2, wziąłbym obiekt parametru lub nawet słownik / HashMap.

Tak jak:

class DokumentHome {

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

Jako wyłączenie odpowiedzialności jestem najpierw programistą C # i JavaScript, a następnie programistą Java.

Knerd
źródło
4
To rozwiązanie, ale nie sądzę, że jest to dobre rozwiązanie (przynajmniej nie w kontekście, w którym spodziewane jest bezpieczeństwo maszynowe).
Doc Brown,
Zgadza się. Z powodu takich przypadków lubię przeciążone metody lub parametry opcjonalne.
Knerd
2
Używanie słownika jako parametru jest łatwym sposobem na zmniejszenie liczby pozornych parametrów do metody, ale przesłania faktyczne zależności metody. Zmusza to osobę wywołującą do szukania gdzie indziej, co dokładnie musi znajdować się w słowniku, czy to w komentarzach, innych wywołaniach metody, czy samej implementacji metody.
Mike Partridge
Używaj słownika tylko dla naprawdę opcjonalnych parametrów, więc podstawowe przypadki użycia nie muszą czytać zbyt dużo dokumentacji.
user949300
0

Myślę, że to dobry kandydat na wzorzec konstruktora. Wzorzec konstruktora jest przydatny, gdy chcesz tworzyć obiekty tego samego typu, ale z różnymi reprezentacjami.

W twoim przypadku miałbym program budujący z następującymi metodami:

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

Następnie możesz użyć konstruktora w następujący sposób:

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

Na marginesie, nie przeszkadza mi kilka prostych przeciążeń - do diabła, platforma .NET używa ich wszędzie w pomocnikach HTML. Chciałbym jednak ponownie ocenić, co robię, jeśli muszę przekazać więcej niż dwa parametry do każdej metody.

CodeART
źródło
0

Myślę, że to builderrozwiązanie może działać w większości scenariuszy, ale w bardziej złożonych przypadkach konfigurator będzie również skomplikowany w konfiguracji , ponieważ łatwo jest popełnić kilka błędów w kolejności metod, jakie metody należy ujawnić, czy nie itd. Wielu z nas preferuje najprostsze rozwiązanie.

Jeśli po prostu utworzysz prosty program do budowania dokumentu i rozłożysz ten kod na różne części (klasy) aplikacji, trudno będzie:

  • zorganizuj : będziesz mieć wiele zajęć budujących dokument na różne sposoby
  • zachowaj : każda zmiana w tworzeniu instancji dokumentu (np. dodanie nowego obowiązkowego zgłoszenia) doprowadzi cię do chirurgii strzelby
  • test : jeśli testujesz klasę, która buduje dokument, będziesz potrzebować dodać kod, aby spełnić instancję dokumentu.

Ale to nie odpowiada na pytanie PO.

Alternatywa dla przeciążenia

Niektóre alternatywy:

  • Przemianować nazwę metody : jeżeli sama nazwa metody jest tworzenie pewne zamieszanie, spróbuj zmienić nazwę metody, aby stworzyć znaczącą nazwę lepiej niż createDocument, jak: createLicenseDriveDocument, createDocumentWithOptionalFields, itd. Oczywiście, może to prowadzić do olbrzymich nazwy metody, więc nie jest to rozwiązanie dla wszystkich przypadków.
  • Użyj metod statycznych . To podejście jest trochę podobne w porównaniu z pierwszą alternatywą powyżej. Można używać znaczących nazw dla każdej sprawy i wystąpienia dokument z metody statyczne Document, jak: Document.createLicenseDriveDocument().
  • Utwórz wspólny interfejs : możesz utworzyć pojedynczą metodę o nazwie createDocument(InterfaceDocument interfaceDocument)i utworzyć różne implementacje dla InterfaceDocument. Na przykład: createDocument(new DocumentMinPagesCount("name")). Oczywiście nie potrzebujesz jednej implementacji dla każdego przypadku, ponieważ możesz utworzyć więcej niż jednego konstruktora dla każdej implementacji, grupując niektóre pola, które mają sens w tej implementacji. Ten wzorzec nazywa się konstruktorami teleskopowymi .

Lub Po prostu trzymaj się rozwiązania przeciążenia . Nawet będąc czasem brzydkim rozwiązaniem, nie ma wielu wad korzystania z niego. W takim przypadku wolę używać metod przeciążania na wydzielonej klasie, takiej jak ta, DocumentoFactoryktórą można wstrzyknąć jako zależność od klas, które muszą tworzyć dokumenty. Mogę organizować i sprawdzać poprawność pól bez złożoności tworzenia dobrego budowniczego i utrzymywania kodu w jednym miejscu.

Dherik
źródło