Czy metoda „start”, „uruchom” lub „uruchom” jest dobrą praktyką?

30

Obecnie pracuję nad bazą kodu, która ma wiele klas, które implementują metodę Start. Wydaje mi się to konstrukcją dwufazową, co zawsze uważałem za złą praktykę. Nie umiem odróżnić tego od konstruktora.

Kiedy należy zastosować metodę początkową zamiast normalnej konstrukcji obiektu?

Kiedy powinienem używać konstruktora?

Edycja: Nie sądzę, że jest to tak istotne, ale językiem programowania jest C #, może on również mieć zastosowanie do Java lub C ++

Dave Hillier
źródło
3
Czy możesz dodać nieco więcej kontekstu? Język? Gwintowany czy pojedynczy wątek? różnica między starta konstruktorem? itd ...
@MichaelT Rozumiem, dlaczego pytasz, ale interesuje mnie ogólna zasada, kiedy jest to właściwe. Obawiam się, że gdybym podał konkretne przykłady z bazy kodu, nad którymi pracuję, odpowiedzi byłyby zbyt skoncentrowane na szczegółach, a nie na moich konkretnych pytaniach.
Dave Hillier
2
@DaveHillier Na przykład w perlu standardową praktyką (i dobrą) jest posiadanie initmetody poza newfunkcją - perldoc.perl.org/perlobj.html . Idiomy jednego języka mogą tam dobrze działać, a nie w innych językach.
1
Przykłady klas z Startmetodami we wspólnych interfejsach API obejmują wątki i stopery.
luiscubal
1
Zalicz mnie do tych, którzy potrzebują próbki kodu, aby zrozumieć, o co właściwie pytasz.
user16764

Odpowiedzi:

44

Start()Sposób (jak Run(), Execute()lub coś podobnego) jest odpowiedni, gdy koszt budowy obiekt jest niska, a koszty stosując jest wysoki. Na przykład: Klasa, która zawiera algorytm optymalizacji najlepszej ścieżki. Ustawienie go z zestawem parametrów ( Xkwadraty za Ykwadraty, przy użyciu takiej metody oceny) jest trywialne , ale wykonanie może zająć trochę czasu. Jeśli chcesz utworzyć 20 takich obiektów, możesz opóźnić wykonanie, aż wszystkie zostaną utworzone - pozwala to na przykład łatwiej zrównoleglić je.

Alternatywnie może być przydatny, gdy nie wiesz, kiedy obiekt będzie potrzebny do uruchomienia - być może dlatego, że jest oparty na danych wejściowych użytkownika lub logice, która wybiera z listy możliwości.

Zakłada się oczywiście, że Start()jest to użyteczna metoda na obiekcie, a nie odpowiednik Initialize()metody. Jeśli jest to dodatkowy sposób na ustawienie większej liczby parametrów, nie powinien istnieć.

Bobson
źródło
1
Innym zastosowaniem metody początkowej jest sytuacja, gdy wykonane działanie tworzy również nowy wątek roboczy lub licznik czasu. Konstruktor nie powinien wykonywać tego rodzaju ciężkiego podnoszenia ani generować znaczących efektów ubocznych (takich jak tworzenie nowego wątku).
Ziv
50

Code Complete (i wiele innych zasobów inżynierii oprogramowania) kładzie nacisk na dopasowanie twoich klas do rzeczywistych obiektów. Uważam, że podstawową przyczyną tego jest fakt, że bardziej prawdopodobne jest, że naprawdę rozumiesz, co wdrażasz, niż przekłamywanie nienamacalnego pomysłu.

Jeśli jesteś subskrybentem tej teorii, nie widzę nic złego w dodawaniu Start()metody do dowolnej klasy, która powinna, gdyby był to prawdziwy obiekt, również mieć stan spoczynku. Jeśli nie ma sensu istnienie obiektu podczas jego działania (lub w ogóle nie ma sensu, aby obiekt działał), powiedziałbym, że jest to zła praktyka.

Dan Albert
źródło
16
Dobra analogia. Warto zauważyć, że Start()może on odpowiadać albo włącznikowi / wyłącznikowi (jak włącznik światła), który powinien następnie mieć przycisk Stop(), lub przyciskowi (jak przycisk Drukuj na kserokopiarce), gdzie uruchamia się, a następnie uruchamia, aż do zakończenia.
Bobson,
3
+1 dobrze powiedziane i witamy w P.SE, takie odpowiedzi to dobry początek.
Jimmy Hoffa
14

Możesz użyć leniwej inicjalizacji.

W programowaniu komputerowym leniwa inicjalizacja to taktyka opóźniania tworzenia obiektu, obliczania wartości lub innego kosztownego procesu do momentu, gdy jest to potrzebne.

W ten sposób unikasz łączenia czasowego, co oznacza, że ​​konsument twojej klasy musi wywoływać określone metody w określonej kolejności. start()Najpierw musisz zadzwonić , aby dowiedzieć się, jak działa klasa, co jest złe, ponieważ możesz to zmienić w przyszłości.

Opóźnij kosztowną inicjalizację, dopóki nie będzie potrzebna.

Przykład:

public class FooClass{

    private ExpensiveResource resource;
    private CheapResource cheap;

    public  FooClass(String someParameter){
        // constructor: initialize CheapResource cheap 
            // but NOT ExpensiveResource resource
    }

    public ExpensiveResource getExpensiveResource(){
        if (resource == null) {
            this.initializeExpensiveResource();     
        }
        return this.resource
    }

    public String getExpensiveResourceName(){
        if (resource == null) {
            this.initializeExpensiveResource();     
        }
        return this.resource.getName();
    }   

    public CheapResource getCheapResource(){
        return this.cheap;
    }

    private initializeExpensiveResource(){
        // do expensive initialization of field "resource"
    }

}

public class Test{
    public static void main (String args[]){

        FooClass foo = new FooClass("some string");
        CheapResource cr = foo.getCheapResource();
        String s = foo.getExpensiveResourceName(); 
          // just now is the expensive resource initialized

    }
}
Tulains Córdova
źródło
5
Kolejną zaletą leniwej inicjalizacji, na którą warto zwrócić uwagę, jest to, że utworzenie bardzo wirtualnego serwera proxy zajmuje niewiele wysiłku . W zależności od sytuacji może to być bardzo przydatne do wyświetlania czegoś podczas oczekiwania na załadowanie zasobu (szczególnie przydatne do rzeczy takich jak zdalne obrazy). Opierając się na pierwotnym pytaniu, nie sądzę, by to było właśnie to, o co zabiegał OP, ale pomyślałem, że warto o tym wspomnieć.
Dan Albert
@DanAlbert masz rację, nie o to prosiłem, ale wciąż jest interesujący
Dave Hillier