Wiele parametrów konstruktora czy klasa abstrakcyjna?

0

Refaktoruje ostatnio bardzo dużo słabego kodu w pracy i w trakcie usuwania duplikacji zacząłem się zastanawiać nad pewną kwestią. Załóżmy, że wydzielam sobie kod pewnego panelu GUI. Panel jest używany w wielu miejscach i jest generalnie identyczny, ale różni się:

  • labelkami
  • tooltipami
  • akcjami jak ktoś cośtam kliknie na przykład

Gdyby tych parametrów było mało i gdyby nie było tam "akcji" to pewnie bez zastanowienia wrzucałbym je przez konstruktor. Liczbę parametrów zawsze można ograniczyć jakimś DTO ale to taki sobie pomysł bo to DTO nigdzie nie byłoby używane a jedynie tworzone w miejscu tworzenia panelu z parametrów znanych w chwili kompilacji. Więc dość słabo. Dodatkowo "akcje" trzeba by wrzucać jako jakieś lambdy albo funktory.
Dodatkowo część tych parametrów może być zostawiona jako domyślna, co mocno komplikuje opcje z konstruktorem, bo albo mamy mother of all constructors a potem kupę nulli jako parametry albo mamy milion konstruktorów (mało możliwe bo niektóre parametry są tego samego typu ;) ).

W związku z tym pomyślałem że można to zrobić z zupełnie innej strony. Zrobić klasę abstrakcyjną z metodami w stylu getXYZTooltip() a wymagane akcje zrobić jako metody abstrakcyjne. Wtedy tworząc obiekt musimy zaimplementować wymagane metody i ewentualnie przeładować te gdzie nie chcemy używać opcji domyślnych. Tylko że to rozwiązanie wydaje mi się trochę dziwne, bo jednak tworzymy nową klasę (choćby i anonimową) podczas gdy w rzeczywistości te klasy różnią się właściwie tylko parametrami (szczególnie jeśli uznamy lambdy za parametry).

Co o tym myślicie?
@somekind @Koziołek @Krolik @niezdecydowany @karolinaa @msm @winerfresh @stryku @Patryk27 @katelx @WhiteLightning @datdata @spartanPAGE @Wizzie @Azarien @Satirev @MarekR22 @Afish @panryz @Wibowit @azalut i więcej mi się nie chce wymieniać, ale niech się nikt nie czuje pominięty! (kolejność losowa!)

9

Pokaż kod :P

4

Może po prostu builder?

1

w tym panelu się layout zmienia? czy wszystko zostaje tak samo tylko "napisowe elementy" sie zmieniają?

w panelu zmieniają się też np buttony? bo jak przeczytałem to pomyslalem, że jeśli występowałby taki związek między tymi elementami, że np: jest buton, każdy buton ma label, kazdy label ma tooltip i jeszcze action na kliknięcie to możnaby stworzyć obiekt upakowujący te pare obiektów i takie zestawy dodawać przez konstruktor i zrobić jakiś limit (nie wiem czy to ta sama idea o której mówisz pisząc o DTO). Ale to wtedy jeśli cały layout albo chociaż takie "paczki" obiektów sie zmieniaja na tym twoim panelu

Co do twojego pomysłu, mówisz tylko o akcjach na kliknięcie? czy chodzi o taką defaultową klase dla panelu/elementów gui i ew. reimplementacji tego co akurat potrzeba? (tzn np getXYZTooltip() zmieniasz napis tipa) bo nie do końca łapie

3

Wujek Bloch faktycznie, powie Builder :) ale ciężko w ogóle powiedzieć, bo jeżeli masz już zdefiniowane że ta klasa ma 5 tooltipów (

getXYZTooltip() 

) i one są zawsze wymagane, to raczej ciężko zrobić z tego "pikny" kod :D Zamiast tej klasy abstrakcyjnej, ciała tych metod (które wcześniej chciałeś po prostu nadpisać) lepiej chyba podać lambdą - nie musisz za każdym razem robić nowej anonimowej klasy (+10000 do wydajności :)) a i lepiej wygląda (i te akcje mogą być faktycznie podawane builderem)


setXXTooltip(p->p.costamZrob()).setYYTooltip(p->p.sendWiadomoscDoGdziestam).setZZZTooltip(p::costam);

A tak serio to napisałem tylko po to żeby poczuć się inteligientny :D:D:D w końcu umiem tylko klepać formy :D:D:D:D:D:D

0

@mychal builder to faktycznie jest klasyczne rozwiązanie jeśli chodzi o problem z konstruktorem, niemniej nadal będę miał gigantyczną "wiązankę" tworzącą taki panel. A mam okienko gdzie jest ich kilka obok siebie ;) Rozwiązanie z klasą abstrakcyjną pozwala wyrzucić ten kod do osobnej klasy i na oko wygląda trochę czytelniej.

@Wibowit @datdata akurat kod nie ma tu specjalnie wiele do rzeczy. Chodzi mi raczej o taką teoretyczną rozkminę ;)

@azalut w panelu nie zmieniają się komponenty, tzn masz tam jakieś pole tekstowe, jakieś buttony etc i to jest stałe. Zmieniają się labelki, tooltipy i akcje. Jeśli chodzi o to nadpisywanie to wyobraź sobie że np. metoda getTooltipForTextFieldX() defaultowo zwraca pustego stringa a jak w danym miejscu chcesz mieć tooltip to ja przeładowujesz. Trochę tak jak np. javowe klasy XYZAdapter dla ActionListenerów.

2

A są jakieś powielające się zbiory własności? Jeśli tak, to możesz zrobić metodę klonującą w budowniczym oraz metody dodające zachowania, np metoda typu:

PanelBuilder addYellowTooltips() {
  tooltipsColor = Yellow;
  return this;
}

Później możesz mieć konstrukcje tego typu:

PanelBuilder genericBuilder = new PanelBuilder().setA(blabla).setB(bleble);
PanelBuilder passwordBuilder = genericBuilder.clone().setTitle("Password reset").showLabel(Labels.PasswordLabel);

Jeśli chodzi o dowolne składanie akcji to możesz zrobić interfejs ze wszystkimi wspólnymi akcjami. Do tego klasę, która ma puste lub domyślne implementacje każdej akcji. Następnie możesz użyć wzorca https://pl.wikipedia.org/wiki/%C5%81a%C5%84cuch_zobowi%C4%85za%C5%84_(wzorzec_projektowy) (chain of responsibility) do zaimplementowania składania zachowań. Kod mógłby być taki:

interface Actions {
  void actionA();
  void actionB();
  void actionC();
}

class ActionsAdapter {
  void actionA() {}
  void actionB() {}
  void actionC() {}
}

class ActionAdapterChain extends ActionsAdapter {
  final ActionsAdapter previous;
  // konstruktor

 // metody delegujące do previous
}

class PanelBuilder {
  Actions actions = new ActionsAdapter();
  ...
  PanelBuilder blinkOnClick() {
    Actions oldActions = actions;
    actions = new ActionAdapterChain(actions) {
      @Override void actionA() { 
        oldActions.actionA(); // albo można olać tę linijkę
        blink(); 
      }
    }
    return this;
  }
  ...
  PanelBuilder genericBuilder = ...
  PanelBuilder blinkingBuilder = genericBuilder.clone().blinkOnClick();
}
0

@Shalom już chyba rozumiem, wydaje się dobre ale ja to widze jakos tak:
zrobić zwykłą klasę, nie abstrakcyjna i defaulotowo zrobić tak jak mowisz, na przyklad.. hm.. mieć pare pól z defaultowymi akcjami. Dla tipa bedzie to pusty String, a dla akcji na klikniecie bedzie do jakis @FunctionalInterface
I teraz - zrobisz obiekt panelu i wszystko jest defaultowo, a jesli chcesz zmienic zachowanie np dla klikniecia butonu to robisz dla każdego pola setter i wstawiasz nowego stringa albo nową funkcję

(jesli dobrze cie rozumiem) to wyprodukowaloby troche mniej kodu niz abstrakcyjna anonimowa klasa i implementowanie tego co chcesz, szczegolnie, ze lambdy mozesz tam wrzucic a to "jednolinijkowce"
Jesli takich nie-defaultowych wymagan byloby nie duzo to kod tez nie bylby jakis rozwlekly
ma to sens jakiś? bo to nagle mi przyszlo do glowy :D

0

Jeszcze tego w taki sposób nie robiłem, ale czy nie jako pomoc w tego typu sytuacjach nie powstało to:

https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

0

Jak już taka rozkminka to może FluentAPI? Domyślnie masz jeden domyślny konstruktor, który ustawia wszystko na wartość zerową/pustą, następnie piszesz sobie jakieś extension functions, żeby działało to tak:

Potrzebujesz okienko z jednym tooltipem i jedną akcją:

      BaseWindow.Create().WithTooltip("id", "text").WithButton("buttonID").WithAction("componenID", () => { /* ... */ });

Żeby nie trzeba było powtarzać kodu do tworzenia tych samych "komponentów" w różnych okienkach, może i coś takiego:

      var sharedComponents =  ComponentsCollection.Create().WithTooltip("id", "text").WithButton("buttonID").WithAction("componenID", () => { /* ... */ });
      var customWindow1 = BaseWindow.Create().WithComponents(sharedComponents).WithButton("buttonID2").WithAction("buttonID2", () => { /* ... */});
      var customWindow2 = BaseWindow.Create().WithComponents(sharedComponents).WithTooltip("ID", "TEXT2");

Nie wiem czy to dobry pomysł :D

1 użytkowników online, w tym zalogowanych: 0, gości: 1