Korzystam z biblioteki strony trzeciej. Przekazują mi POJO, które dla naszych celów i celów jest prawdopodobnie realizowane w następujący sposób:
public class OurData {
private String foo;
private String bar;
private String baz;
private String quux;
// A lot more than this
// IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
OurData(/* I don't know what they do */) {
// some stuff
}
public String getFoo() {
return foo;
}
// etc.
}
Z wielu powodów, w tym między innymi enkapsulacji API i ułatwiania testów jednostkowych, chcę opakować ich dane. Ale nie chcę, aby moje podstawowe klasy były zależne od ich danych (ponownie, z powodów testowych)! Więc teraz mam coś takiego:
public class DataTypeOne implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
}
public class DataTypeTwo implements DataInterface {
private String foo;
private int bar;
private double baz;
public DataTypeOne(String foo, int bar, double baz, String quux) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
this.quux = quux;
}
}
A potem to:
public class ThirdPartyAdapter {
public static makeMyData(OurData data) {
if(data.getQuux() == null) {
return new DataTypeOne(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
);
} else {
return new DataTypeTwo(
data.getFoo(),
Integer.parseInt(data.getBar()),
Double.parseDouble(data.getBaz()),
data.getQuux();
);
}
}
Ta klasa adaptera jest połączona z kilkoma innymi klasami, które MUSZĄ wiedzieć o interfejsie API innej firmy, ograniczając jej wszechobecność w pozostałej części mojego systemu. Jednak ... to rozwiązanie jest BRUTTO! W Clean Code, strona 40:
Więcej niż trzy argumenty (poliadowe) wymagają bardzo specjalnego uzasadnienia - i i tak nie powinny być używane.
Rzeczy, które rozważałem:
- Tworzenie obiektu fabrycznego zamiast statycznej metody pomocniczej
- Nie rozwiązuje problemu posiadania bajillionowych argumentów
- Tworzenie podklasy DataTypeOne i DataTypeTwo, która ma zależny konstruktor
- Nadal ma chroniony poliadycznie konstruktor
- Twórz całkowicie osobne implementacje zgodne z tym samym interfejsem
- Wiele powyższych pomysłów jednocześnie
Jak sobie z tym poradzić?
Uwaga: nie jest to sytuacja warstwy antykorupcyjnej . Nie ma nic złego w ich API. Problemy są następujące:
- Nie chcę mieć MOICH struktur danych
import com.third.party.library.SomeDataStructure;
- Nie mogę zbudować ich struktur danych w moich testowych przypadkach
- Moje obecne rozwiązanie skutkuje bardzo bardzo dużą liczbą argumentów. Chcę, aby liczba argumentów była niska, BEZ przekazywania ich struktur danych.
- To pytanie brzmi „ czym jest warstwa antykorupcyjna?”. Moje pytanie brzmi: „ Jak mogę użyć wzoru, dowolnego wzoru, aby rozwiązać ten scenariusz?”
Nie pytam też o kod (inaczej to pytanie byłoby na SO), po prostu proszę o wystarczającą odpowiedź, aby umożliwić mi skuteczne napisanie kodu (czego to pytanie nie zapewnia).
źródło
The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Odpowiedzi:
Strategia, której użyłem, gdy istnieje kilka parametrów inicjalizacji, polega na stworzeniu typu zawierającego tylko parametry inicjalizacji
Następnie konstruktor DataTypeTwo pobiera obiekt DataTypeTwoParameters, a DataTypeTwo jest konstruowany poprzez:
Daje to wiele możliwości wyjaśnienia, jakie są wszystkie parametry wchodzące w skład DataTypeTwo i co one oznaczają. Można również podać rozsądne wartości domyślne w konstruktorze DataTypeTwoParameters, aby można było ustawić tylko wartości, które należy ustawić, w dowolnej kolejności, którą lubi konsument interfejsu API.
źródło
Integer.parseInt
? W seterze, czy poza klasą parametrów?p.bar = Integer.parseInt("4")
.DataTypeTwoParameters
naDataTypeTwo
.Naprawdę masz tutaj dwie osobne obawy: owijanie API i utrzymywanie niskiej liczby argumentów.
Podczas pakowania interfejsu API chodzi o zaprojektowanie interfejsu od zera, nie znając niczego poza wymaganiami. Mówisz, że nie ma nic złego w ich interfejsie API, a następnie na tym samym oddechu wypisz kilka rzeczy błędnych w ich interfejsie API: testowalność, konstruowalność, zbyt wiele parametrów w jednym obiekcie itp. Napisz API, które chciałbyś mieć. Jeśli wymaga to wielu obiektów zamiast jednego, zrób to. Jeśli wymaga to zawinięcia o jeden poziom wyżej , zrób to z obiektami, które tworzą POJO.
Po uzyskaniu pożądanego interfejsu API liczba parametrów może już nie stanowić problemu. Jeśli tak, należy wziąć pod uwagę wiele typowych wzorców:
Zauważ, że te wzorce tworzenia często kończą się wywoływaniem konstruktora poliadowego, co powinieneś uznać za dobre, gdy jest zamknięty. Problem z konstruktorami poliadowymi polega na tym, że nie wywołuje się ich raz, lecz wtedy trzeba je wywoływać za każdym razem, gdy trzeba zbudować obiekt.
Zauważ, że zwykle o wiele łatwiej i łatwiej jest utrzymać interfejs API, przechowując referencję do
OurData
obiektu i przekazując wywołania metod, a nie próbując ponownie wdrożyć jego elementy wewnętrzne. Na przykład:źródło
OurData
obiektu” - tego właśnie staram się unikać, przynajmniej w klasie podstawowej, aby upewnić się, że nie ma zależności.DataInterface
. Tworzysz kolejną implementację dla swoich próbnych obiektów.Myślę, że zbyt surowo interpretujesz zalecenie wuja Boba. W przypadku normalnych klas, z logiką i metodami oraz konstruktorami itp., Konstruktor poliadowy rzeczywiście przypomina zapach kodu. Ale w przypadku czegoś, co jest ściśle kontenerem danych, który odsłania pola i jest generowane przez obiekt, który jest już zasadniczo obiektem fabryki, nie sądzę, że jest tak źle.
Państwo może wykorzystać wzór Object parametrów, jak sugeruje w komentarzu, można owinąć te parametry konstruktora dla ciebie, co lokalny typ danych jest wrapper jest już zasadniczo obiektem parametru. Wszystko, co zrobisz w swoim obiekcie Parameter, to spakowanie parametrów (Jak to utworzysz? Konstruktorem poliadowym?), A następnie rozpakowanie ich sekundę później w obiekt, który jest prawie identyczny.
Jeśli nie chcesz wystawiać seterów dla swoich pól i nazywać ich, myślę, że trzymanie się konstruktora poliadowego w dobrze zdefiniowanej i zamkniętej fabryce jest w porządku.
źródło