Metoda tworzenia statycznego - zalety i wady w porównaniu z konstruktorami

11

Jakie są zalety i wady posiadania statycznych metod tworzenia obiektów nad konstruktorami?

class Foo {
  private Foo(object arg) { }

  public static Foo Create(object arg) {
    if (!ValidateParam(arg)) { return null; }
    return new Foo(arg);
  }
}

Niewiele mogę wymyślić:

Plusy:

  • Zwraca null zamiast zgłaszania wyjątku (nazwij go TryCreate). Może to sprawić, że kod będzie bardziej zwięzły i czysty po stronie klienta. Klienci rzadko oczekują awarii konstruktora.
  • Twórz różne rodzaje obiektów z wyraźną semantyką, np. CreatFromName(String name)ICreateFromCsvLine(String csvLine)
  • W razie potrzeby może zwrócić buforowany obiekt lub pochodną implementację.

Cons:

  • Mniej wykrywalny, trudniejszy do przeglądania kod.
  • Niektóre wzorce, takie jak serializacja lub odbicie są trudniejsze (np. Activator<Foo>.CreateInstance())
dbkk
źródło
1
Jest wolniejszy. W każdym razie musisz obsłużyć odwołanie zerowe.
Amir Rezaei
1
@Amir Handling null is ( Foo x = Foo.TryCreate(); if (x == null) { ... }). Obsługa wyjątku ctor to ( Foo x; try { x = new Foo(); } catch (SomeException e) { ... }). Podczas wywoływania normalnej metody wolę wyjątki od kodów błędów, ale przy tworzeniu obiektów TryCreatewydaje się czystsze.
dbkk
Czy w swojej walidacji dokonałbyś kontroli dowolnego rodzaju?
Amir Rezaei
W odniesieniu do drugiego Pro, „Twórz różne rodzaje obiektów z wyraźną semantyką, np. CreatFromName (nazwa ciągu) i CreateFromCsvLine (ciąg csvLine)”, możesz lepiej tworzyć Namei pisać CsvLine, niż wyrażać wymagania za pomocą nazw metod. Pozwoliłoby to na przeciążenie Utwórz. Używanie ciągów znaków w obu przypadkach może być uważane za „prymitywną obsesję” (zakładając, że nie dokonałeś tego wyboru ze znanych powodów wydajności). Sprawdź Object Calisthenics, aby uzyskać zabawny sposób na zbadanie tego.
bentayloruk

Odpowiedzi:

7

Największą wadą statycznych „ twórców ” jest prawdopodobnie ograniczenie dziedziczenia. Jeśli ty lub użytkownik twojej biblioteki wyprowadzasz klasę z twojej Foo, Foo::Create()staje się prawie bezużyteczny. Cała zdefiniowana tam logika będzie musiała zostać przepisana ponownie w odziedziczonym Create().

Sugerowałbym kompromis: zdefiniuj konstruktor z trywialną logiką inicjowania obiektu, która nigdy nie zawiedzie / wyrzuca, następnie zdefiniuj twórcę (-ów) z buforowaniem, alternatywną konstrukcją itp. To pozostawia możliwość czerpania, a Ty zyskujesz mając twórców dla danej klasy .

mojuba
źródło
1
W praktyce nie jest to tak naprawdę problemem, ponieważ większość klas nie jest zaprojektowana do dziedziczenia (i dlatego powinna być zapieczętowana / ostateczna). Jeśli później okaże się, że chcesz otworzyć klasę do dziedziczenia, zawsze możesz przenieść dowolną logikę inicjalizacji do chronionego konstruktora.
Doval,
7

Utwórz to metoda fabryczna. Kiedy czuję taką potrzebę, wdrażam wzorzec Factory i definiuję interfejs do reprezentowania kontraktu na tworzenie obiektu, a także klasę implementacyjną, która jest następnie wprowadzana w razie potrzeby. Zachowuje to zalety przenoszenia odpowiedzialności za tworzenie poza klasę, ale unika (lub przynajmniej czyni bardziej oczywistym) ograniczeń wykonywania tworzenia obiektów poza konstruktorem klasy.

quentin-starin
źródło