2012-07-07 16 views
21

Mam klasę JAVA z dużą ilością pól. Powinny one zasadniczo zostać ustawione na etapie konstruktora i nigdy się nie zmieniać. Semantycznie klasa jest niezmienna.Konstruktor Java "niezmiennej klasy" z wieloma polami z wartościami domyślnymi?

public class A{ 
    final int a; 
    final short b; 
    final double e; 
    final String f; 
    final String g; 
    //and more 
} 

Problem polega na tym, że zwykle te pola mają wartości domyślne i dlatego nie chcę zawsze obciążać użytkownika konstruktorem wszystkimi. W większości przypadków wystarczy ustawić kilka z nich. Istnieje kilka sposobów rozwiązania tego problemu:

  1. Potrzebowałbym dużo konstruktora z innym podpisem.
  2. Utwórz grupę ustawionych metod dla tych pól i ustaw tylko te wartości inne niż domyślne. Ale to w jakiś sposób wskazuje na inną semantykę niż niezmienną naturę.
  3. Utwórz nową klasę parametrów, która jest zmienna i użyj tej klasy jako konstruktora.

Nic z tego nie jest w pełni satysfakcjonujące. Czy istnieje inne podejście? Dzięki. Jednym ze sposobów

Odpowiedz

27

chciałbym użyć do tworzenia parametru kombinacji klasy parametrów i płynnie budowniczy API (co należy robić tak!):

public class A { 
    private final int a; 
    private final short b; 
    private final double e; 
    private final String g; 

    public static class Aparam { 
     private int a = 1; 
     private short b = 2; 
     private double e = 3.141593; 
     private String g = "NONE"; 

     public Aparam a(int a) { 
      this.a = a; 
      return this; 
     } 

     public Aparam b(short b) { 
      this.b = b; 
      return this; 
     } 

     public Aparam e(double e) { 
      this.e = e; 
      return this; 
     } 

     public Aparam g(String g) { 
      this.g = g; 
      return this; 
     } 

     public A build() { 
      return new A(this); 
     } 
    } 

    public static Aparam a(int a) { 
     return new Aparam().a(a); 
    } 

    public static Aparam b(short b) { 
     return new Aparam().b(b); 
    } 

    public static Aparam e(double e) { 
     return new Aparam().e(e); 
    } 

    public static Aparam g(String g) { 
     return new Aparam().g(g); 
    } 

    public static A build() { 
     return new Aparam().build(); 
    } 

    private A(Aparam p) { 
     this.a = p.a; 
     this.b = p.b; 
     this.e = p.e; 
     this.g = p.g; 
    } 

    @Override public String toString() { 
     return "{a=" + a + ",b=" + b + ",e=" + e + ",g=" + g + "}"; 
    } 
} 

następnie utworzyć instancji A:

A a1 = A.build(); 
A a2 = A.a(7).e(17.5).build(); 
A a3 = A.b((short)42).e(2.218282).g("fluent").build(); 

Klasa A jest niezmienna, parametry są opcjonalne, a interfejs jest płynny.

+1

Nie potrzebujesz nawet modułów pobierających w konstruktorze, a konstruktor A może być prywatny. Pozwala to również na sprawdzanie parametrów w metodzie build() zamiast w konstruktorze. –

+0

Prawda. Zastanawiałem się nad uczynieniem cora A prywatnym. To prawdopodobnie czystsze. –

+0

Kolejną zaletą jest to, że budowniczy może zwrócić instancję A, ABis lub ATer (Abis i Ater będącą podklasą A), w zależności od parametrów. –

19

dwie rzeczy można zrobić:

+4

+1 dla budowniczego. –

+0

Nie sądzę, że wzorzec konstruktora jest właściwy dla niezmiennej klasy. – zggame

+0

@zggame: oczywiście, że nie! Używasz budowniczego do zbudowania innego obiektu. –

0

Interesującą opcją jest utworzenie konstruktora, że ​​trwa Map<String,Object> jako wkład, który zawiera wartości, które użytkownik chce określić.

Konstruktor może użyć wartości podanej na mapie, jeśli jest obecna, lub wartości domyślnej w przeciwnym razie.

EDIT:

myślę losowe downvoters całkowicie brakowało punkt - to nie zawsze będzie najlepszym wyborem, ale jest to przydatna technika, która ma kilka zalet:

  • Jest zwięzły i unika potrzeby tworzenia oddzielnych klas konstruktorów/konstruktorów.
  • Pozwala na łatwą programową konstrukcję zestawów parametrów (np. Jeśli konstruujesz obiekty z przeanalizowanego pliku DSL)
  • Ta technika jest często używana i udowodniona, że ​​działa w językach dynamicznych. Wystarczy napisać porządne testy
+1

to prowadzi do wielu błędów, głównie ze zgodnością typów, powiedziałbym, że w tym przypadku podejście konstruktora byłoby lepsze (zgodnie z sugestią Jordão). –

+0

Cóż, jest to technika z dynamicznych języków .... handlujesz statycznym sprawdzaniem typu wygoda/elastyczność. Do ciebie, jeśli podoba ci się podejście, ale odkryłem, że to nie problem, jeśli napiszesz dobre testy. – mikera

+0

To jest sposób JavaScript i dobry w językach z dynamicznym typowaniem, ale nie zrobiłbym tego w Javie. Mogłaby działać, gdyby wszystkie argumenty były tego samego typu. Sądzę jednak, że Bob Martin ma coś do powiedzenia na temat "przekazywania map hashów", co nie jest dobrym pomysłem.Ani -1 ani +1. –

0

Posiadanie wielu pól może wskazywać, że jedna klasa robi za dużo.

Może można podzielić klasę na kilka niezmiennych klas i przekazać wystąpienia tych klas do konstruktorów innych klas. Ograniczyłoby to liczbę konstruktorów.

1

To tylko półpoważna sugestia, ale możemy zmodyfikować mikera's answer, aby była bezpieczna dla typów.

Say mamy:

public class A { 
    private final String foo; 
    private final int bar; 
    private final Date baz; 
} 

Następnie piszemy:

public abstract class AProperty<T> { 
    public static final AProperty<String> FOO = new AProperty<String>(String.class) {}; 
    public static final AProperty<Integer> BAR = new AProperty<Integer>(Integer.class) {}; 
    public static final AProperty<Date> BAZ = new AProperty<Date>(Date.class) {}; 

    public final Class<T> propertyClass; 

    private AProperty(Class<T> propertyClass) { 
     this.propertyClass = propertyClass; 
    } 
} 

oraz:

public class APropertyMap { 
    private final Map<AProperty<?>, Object> properties = new HashMap<AProperty<?>, Object>(); 

    public <T> void put(AProperty<T> property, T value) { 
     properties.put(property, value); 
    } 
    public <T> T get(AProperty<T> property) { 
     return property.propertyClass.cast(properties.get(property)); 
    } 
} 

miłośników zaawansowanych wzorców projektowych i/lub niejasnych sztuczek Java rozpozna to jako pojemnik heterogeniczny typu. Po prostu bądź wdzięczny, że nie użyłem też getGenericSuperclass().

Następnie z powrotem w klasie docelowej:

public A(APropertyMap properties) { 
    foo = properties.get(AProperty.FOO); 
    bar = properties.get(AProperty.BAR); 
    baz = properties.get(AProperty.BAZ); 
} 

To wszystko jest wykorzystywane tak:

APropertyMap properties = new APropertyMap(); 
properties.put(AProperty.FOO, "skidoo"); 
properties.put(AProperty.BAR, 23); 
A a = new A(properties); 

Tylko dla lulz, możemy nawet dać mapie płynny interfejs:

public <T> APropertyMap with(AProperty<T> property, T value) { 
    put(property, value); 
    return this; 
} 

Który pozwala dzwoniącym pisać:

A a = new A(new APropertyMap() 
    .with(AProperty.FOO, "skidoo") 
    .with(AProperty.BAR, 23)); 

Istnieje wiele drobnych ulepszeń, które można w tym celu wprowadzić. Typy w AProperty mogą być obsługiwane bardziej elegancko. APropertyMap może mieć statyczną fabrykę zamiast konstruktora, pozwalając na bardziej płynny styl kodu, jeśli robisz coś takiego. APropertyMap może rozwinąć metodę build, która wywołuje konstruktor A, zasadniczo przekształcając go w program budujący.

Niektóre z tych obiektów można uczynić bardziej ogólnymi. AProperty i APropertyMap może mieć ogólne klasy bazowe, które wykonały bity funkcjonalne, z bardzo prostymi podklasami specyficznymi dla A.

Jeśli czujesz się szczególnie przedsiębiorcą, a obiekty domeny są jednostkami JPA2, możesz użyć atrybutów metamodelu jako obiektów właściwości. Pozostawia to mapę/konstruktorowi nieco więcej pracy, ale wciąż jest dość proste; Mam generycznego konstruktora pracującego w 45 liniach, z podklasą na jednostkę zawierającą jedną metodę jednoliniową.

+0

Nice. Uwielbiam pisanie statyczne. Dobry kompromis, może będę mógł go użyć do innego przypadku, w którym mamy gigantyczną Mapę . Dzięki. – zggame

Powiązane problemy