Używać metody konstruktora lub setera?

16

Pracuję nad kodem interfejsu użytkownika, w którym mam Actionklasę, coś takiego -

public class MyAction extends Action {
    public MyAction() {
        setText("My Action Text");
        setToolTip("My Action Tool tip");
        setImage("Some Image");
    }
}

Kiedy ta klasa Action została utworzona, prawie zakładano, że Actionklasy nie będzie można dostosowywać (w pewnym sensie - jej tekst, etykieta lub obraz nie zostaną zmienione w żadnym miejscu w kodzie). Teraz potrzebujemy zmienić tekst akcji w pewnym miejscu w kodzie. Zasugerowałem więc, aby mój współpracownik usunął zakodowany tekst akcji z konstruktora i zaakceptował go jako argument, aby wszyscy byli zmuszeni przekazać tekst akcji. Coś takiego jak ten kod poniżej -

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
}

Uważa jednak, że skoro setText()metoda należy do klasy podstawowej, można ją elastycznie wykorzystać do przekazywania tekstu akcji wszędzie tam, gdzie tworzona jest instancja akcji. W ten sposób nie ma potrzeby zmiany istniejącej MyActionklasy. Więc jego kod wyglądałby mniej więcej tak.

MyAction action = new MyAction(); //this creates action instance with the hardcoded text
action.setText("User required new action text"); //overwrite the existing text.

Nie jestem pewien, czy jest to właściwy sposób radzenia sobie z problemem. Myślę, że w wyżej wymienionym przypadku użytkownik i tak zamierza zmienić tekst, więc dlaczego nie zmusić go podczas konstruowania akcji? Jedyną korzyścią, jaką widzę w oryginalnym kodzie, jest to, że użytkownik może utworzyć klasę Action bez większego zastanowienia nad ustawieniem tekstu.

zswap
źródło
1
Język, którego używasz, nie pozwala na przeciążanie konstruktorów?
Mat.
1
Używam Java, więc tak, pozwala i myślę, że może to być jeden ze sposobów, aby sobie z tym poradzić
zswap
2
Chciałbym zauważyć, że jeśli nie masz publicznego sposobu na ustawienie członków klasy po fakcie, twoja klasa jest faktycznie niezmienna . Zezwalając publicznemu rozgrywającemu, twoja klasa staje się zmienna i być może będziesz musiał wziąć to pod uwagę, jeśli polegałeś na niezmienności.
cbojar
Powiedziałbym, że jeśli musi być ustawiony, aby twój obiekt był poprawny, umieść go w każdym konstruktorze ... jeśli jest opcjonalny (ma rozsądną wartość domyślną) i nie przejmujesz się niezmiennością, umieść go w seterze. Tworzenie instancji obiektu w nieprawidłowy stan lub po utworzeniu instancji powinno być niemożliwe, jeśli jest to możliwe, niemożliwe.
Bill K

Odpowiedzi:

15

Jedyną korzyścią, jaką widzę w oryginalnym kodzie, jest to, że użytkownik może utworzyć klasę Action bez większego zastanowienia nad ustawieniem tekstu.

W rzeczywistości nie jest to korzyść, dla większości celów jest to wadą, aw pozostałych przypadkach nazwałbym to remisem. Co jeśli ktoś zapomni wywołać setText () po zakończeniu budowy? Co się stanie, jeśli tak jest w nietypowym przypadku, na przykład w programie obsługi błędów? Jeśli chcesz naprawdę wymusić ustawienie tekstu, musisz wymusić go w czasie kompilacji, ponieważ tylko błędy w czasie kompilacji są tak naprawdę fatalne . Wszystko, co dzieje się w czasie wykonywania, zależy od wykonania określonej ścieżki kodu.

Widzę dwie wyraźne ścieżki naprzód:

  1. Użyj parametru konstruktora, jak sugerujesz. Jeśli naprawdę chcesz, możesz przekazać nulllub pusty ciąg znaków, ale fakt, że nie przypisujesz tekstu, jest jawny, a nie domyślny. Łatwo jest dostrzec istnienie nullparametru i przekonać się, że pewnie było w nim trochę myśli, ale nie tak łatwo dostrzec brak wywołania metody i ustalić, czy brak takiego zamiaru był zamierzony, czy nie. W przypadku takiego prostego przypadku jest to prawdopodobnie podejście, które wybrałbym.
  2. Użyj wzorca fabrycznego / konstruktora. Może to być przesada w przypadku tak prostego scenariusza, ale w bardziej ogólnym przypadku jest bardzo elastyczny, ponieważ pozwala ustawić dowolną liczbę parametrów i sprawdzić warunki wstępne przed lub podczas tworzenia instancji obiektu (jeśli konstruowanie obiektu jest dużą operacją i / lub klasa może być używana na więcej niż jeden sposób, może to być ogromna zaleta). Szczególnie w Javie jest to również powszechny idiom, a przestrzeganie ustalonych wzorców w używanym języku i frameworku bardzo rzadko jest złą rzeczą.
CVn
źródło
10

Przeciążenie konstruktora byłoby tutaj prostym i bezpośrednim rozwiązaniem:

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
    public MyAction() {
        this("My Action Text");
    }
}

Jest to lepsze niż dzwonienie .setTextpóźniej, ponieważ w ten sposób nic nie musi zostać nadpisane, actionTextod samego początku może być zamierzoną rzeczą.

W miarę ewolucji twojego kodu będziesz potrzebował jeszcze większej elastyczności (co na pewno się wydarzy), skorzystasz z wzorca fabrycznego / konstruktora sugerowanego przez inną odpowiedź.

janos
źródło
Co dzieje się, gdy chcą dostosować drugą właściwość?
kevin cline
3
W przypadku właściwości 2., 3. .. możesz zastosować tę samą technikę, ale im więcej właściwości chcesz dostosować, tym bardziej będzie ona niewygodna. W pewnym momencie bardziej sensowne będzie wdrożenie wzorca fabryka / konstruktor, co również powiedział @ michael-kjorling w swojej odpowiedzi.
janos
6

Dodaj płynną metodę „setText”:

public class MyAction ... {
  ...
  public MyAction setText(String text) { ... ; return this; }
}

MyAction a = new MyAction().setText("xxx");

Co może być bardziej zrozumiałe? Jeśli zdecydujesz się dodać kolejną dostosowywalną właściwość, nie ma problemu.

Kevin Cline
źródło
+1, zgadzam się i dodałem kolejną odpowiedź uzupełniającą o więcej właściwości. Myślę, że płynny interfejs API jest łatwiejszy do zrozumienia, gdy masz więcej niż jedną pojedynczą właściwość jako przykład.
Machado,
Lubię płynne interfejsy, szczególnie dla konstruktorów produkujących niezmienne obiekty! Im więcej parametrów, tym lepiej to działa. Ale patrząc na konkretny przykład w tym pytaniu, zgaduję, że setText()jest on zdefiniowany w klasie Action, z której dziedziczy MyAction. Prawdopodobnie ma już typ nieważnego zwrotu.
GlenPeterson
1

Tak jak powiedział Kevin Cline w swojej odpowiedzi, myślę, że najlepszą drogą jest stworzenie płynnego API . Chciałbym tylko dodać, że płynny interfejs API działa lepiej, gdy masz więcej niż jedną właściwość, której możesz użyć.

Uczyni twój kod bardziej czytelnym, a moim zdaniem łatwiejszym i, aham , „seksownym” do pisania.

W twoim przypadku wyglądałoby to tak (przepraszam za literówkę, minął rok odkąd napisałem mój ostatni program Java):

 public class MyAction extends Action {
    private String _text     = "";
    private String _tooltip  = "";
    private String _imageUrl = "";

    public MyAction()
    {
       // nothing to do here.
    }

    public MyAction text(string value)
    {
       this._text = value;
       return this;
    }

    public MyAction tooltip(string value)
    {
       this._tooltip = value;
       return this;
    }

    public MyAction image(string value)
    {
       this._imageUrl = value;
       return this;
    }
}

A użycie byłoby takie:

MyAction action = new MyAction()
    .text("My Action Text")
    .tooltip("My Action Tool tip")
    .image("Some Image");
Machado
źródło
Zły pomysł, co jeśli zapomną ustawić tekst lub coś ważnego.
Prakhar
1

Porady dotyczące używania konstruktorów lub konstruktorów są ogólnie dobre, ale z mojego doświadczenia wynika, że ​​brakuje niektórych kluczowych punktów dla akcji, które

  1. Być może trzeba je umiędzynarodowić
  2. Prawdopodobnie marketing zmieni się w ostatniej chwili.

Zdecydowanie sugeruję, aby nazwa, etykietka narzędzia, ikona itp. ... zostały odczytane z pliku właściwości, XML itp. Na przykład w przypadku akcji otwierania pliku można przekazać właściwości i szukałaby

File.open.name=Open
File.open.tooltip=Open a file
File.open.icon=somedir/open.jpg

Jest to format dość łatwy do przetłumaczenia na francuski, wypróbowania nowej lepszej ikony itp. Bez czasu programisty lub ponownej kompilacji.

To tylko ogólny zarys, wiele pozostawia czytelnikowi ... Poszukaj innych przykładów internacjonalizacji.

użytkownik949300
źródło
0

Bezużyteczne jest wywoływanie setText (actionText) lub setTooltip („Wskazówka dla mojej akcji”) wewnątrz konstruktora; jest łatwiej (i zyskujesz większą wydajność), jeśli po prostu zainicjujesz bezpośrednio odpowiednie pole:

    public MyAction(String actionText) {
        this.actionText = actionText;
    }

Jeśli zmienisz actionText podczas życia odpowiedniego obiektu MyAction, powinieneś ustawić metodę ustawiającą; jeśli nie, zainicjuj pole tylko w konstruktorze bez podania metody ustawiającej.

Ponieważ podpowiedź i obraz są stałymi, traktuj je jak stałe; mieć pola:

private (or even public) final static String TOOLTIP = "My Action Tooltip";

W rzeczywistości, przy projektowaniu wspólnych obiektów (nie fasoli lub obiektów reprezentujących ściśle struktury danych), źle jest ustawić obiekty ustawiające i pobierające, ponieważ w pewnym sensie blokują one enkapsulację.

m3th0dman
źródło
4
Każdy kompetentny w połowie kompilator i JITter powinny wstawiać wywołania setText () itp., Więc różnica wydajności między wywołaniem funkcji w celu wykonania przypisania, a posiadaniem tego przypisania zamiast wywołania funkcji, powinna być co najwyżej nieistotna i bardziej prawdopodobne niż nie zero.
CVn
0

Myślę, że jest to prawdą, jeśli zamierzamy stworzyć ogólną klasę akcji (jak aktualizacja, która służy do aktualizacji pracownika, działu ...). Wszystko zależy od scenariusza. Jeśli zostanie utworzona konkretna klasa akcji (np. Aktualizacja pracownika) (używana wiele miejsc w aplikacji - Aktualizuj pracownika) z zamiarem zachowania tego samego tekstu, podpowiedzi i obrazu w każdym miejscu w aplikacji (ze względu na spójność). Można więc wykonać stałe kodowanie tekstu, podpowiedzi i obrazu w celu zapewnienia domyślnego tekstu, podpowiedzi i obrazu. Aby zapewnić większą elastyczność, aby je dostosować, powinien mieć odpowiednie metody ustawiające. Mając na uwadze tylko 10% miejsc, musimy to zmienić. Pobieranie tekstu akcji za każdym razem od użytkownika może powodować inny tekst za każdym razem dla tej samej akcji. Jak „Aktualizacja Emp”, „Aktualizacja pracownika”, „Zmiana pracownika” lub „Edycja pracownika”.

Piaszczysty
źródło
Myślę, że przeciążony konstruktor powinien nadal rozwiązać problem. Ponieważ we wszystkich przypadkach „10%” najpierw utworzysz akcję z domyślnym tekstem, a następnie zmienisz tekst akcji za pomocą metody „setText ()”. Dlaczego nie ustawić odpowiedniego tekstu podczas konstruowania akcji?
zswap
0

Pomyśl o tym, jak będą używane instancje, i skorzystaj z rozwiązania, które poprowadzi, a nawet zmusi użytkowników do korzystania z tych instancji we właściwy, a przynajmniej najlepszy sposób. Programista korzystający z tej klasy będzie miał wiele innych rzeczy do zmartwienia i przemyślenia. Ta klasa nie powinna dodawać do listy.

Na przykład, jeśli klasa MyAction ma być niezmienna po zbudowaniu (i ewentualnie innej inicjalizacji), nie powinna mieć metody ustawiającej. Jeśli przez większość czasu będzie używać domyślnego tekstu „My Action Text”, powinien istnieć konstruktor bez parametrów oraz konstruktor, który zezwala na tekst opcjonalny. Teraz użytkownik nie musi myśleć o prawidłowym użyciu klasy w 90% przypadków. Jeśli użytkownik zwykle powinien przemyśleć tekst, pomiń konstruktor bez parametrów. Teraz użytkownik jest zmuszony myśleć w razie potrzeby i nie może przeoczyć niezbędnego kroku.

Jeśli MyActioninstancja musi być zmienna po pełnej budowie, to musisz ustawić tekst. Kuszące jest pominięcie ustawiania wartości w konstruktorze (zasada DRY - „Don't Repeat Yourself”), a jeśli wartość domyślna jest zwykle wystarczająco dobra, zrobiłbym to. Ale jeśli nie jest, wymaganie tekstu w konstruktorze zmusza użytkownika do zastanowienia się, kiedy powinien.

Pamiętaj, że ci użytkownicy nie są głupi . Mają po prostu zbyt wiele prawdziwych problemów, którymi trzeba się martwić. Myśląc o „interfejsie” klasy, możesz sprawić, że nie stanie się on również prawdziwym problemem - i niepotrzebnym.

RalphChapin
źródło
0

W poniższym proponowanym rozwiązaniu nadklasa jest abstrakcyjna i ma wszystkie trzy elementy ustawione na wartość domyślną.

Podklasa ma różne konstruktory, więc programista może ją utworzyć.

Jeśli użyty zostanie pierwszy konstruktor, wszystkie elementy będą miały wartości domyślne.

Jeśli używany jest drugi konstruktor, wartość początkowa jest przypisywana do elementu actionText, pozostawiając pozostałym dwóm elementom domyślną wartość ...

Jeśli używany jest trzeci konstruktor, tworzysz go z nową wartością dla actionText i toolTip, pozostawiając imageURl z wartością domyślną ...

I tak dalej.

public abstract class Action {
    protected String text = "Default action text";
    protected String toolTip = "Default action tool tip";
    protected String imageURl = "http://myserver.com/images/default.png";

    .... rest of code, I guess setters and getters
}

public class MyAction extends Action {


    public MyAction() {

    }

    public MyAction(String actionText) {
        setText(actionText);
    }

    public MyAction(String actionText, String toolTip_) {
        setText(actionText);
        setToolTip(toolTip_);   
    }

    public MyAction(String actionText, String toolTip_; String imageURL_) {
        setText(actionText);
        setToolTip(toolTip_);
        setImageURL(imageURL_);
    }


}
Tulains Córdova
źródło