2009-03-23 11 views
42

Baza kodu, w której pracuję, ma obiekt o nazwie Para, gdzie A i B są typami pierwszej i drugiej wartości w Pary. Uważam ten obiekt za obraźliwy, ponieważ zostaje użyty zamiast obiektu z wyraźnie nazwanymi członkami. Więc uważam to:Dlaczego używać krotek zamiast obiektów?

List<Pair<Integer, Integer>> productIds = blah(); 
// snip many lines and method calls 

void doSomething(Pair<Integer, Integer> id) { 
    Integer productId = id.first(); 
    Integer quantity = id.second(); 
} 

Zamiast

class ProductsOrdered { 
    int productId; 
    int quantityOrdered; 
    // accessor methods, etc 
} 

List<ProductsOrderded> productsOrdered = blah(); 

wiele innych zastosowań Pair w kodzie są podobnie źle pachnące.

I Googled krotki i wydają się być często źle rozumiane lub używane w wątpliwych sposobów. Czy istnieje przekonujący argument za lub przeciw ich użyciu? Mogę docenić brak chęci tworzenia olbrzymich hierarchii klas, ale czy istnieją realistyczne bazy kodów, w których hierarchia klas wybuchałaby, gdyby nie użyto krotek?

Odpowiedz

36

Po pierwsze, krotka jest szybka i łatwa: zamiast pisać zajęcia za każdym razem, gdy chcesz umieścić 2 rzeczy razem, jest szablon, który robi to za Ciebie.

Po drugie, są ogólne. Na przykład w C++ std :: map używa std :: pair klucza i wartości. W ten sposób można użyć dowolnej pary, zamiast tworzyć jakąś klasę wrappera z metodami accessor dla każdej permutacji dwóch typów.

Wreszcie, są przydatne do zwracania wielu wartości. Naprawdę nie ma powodu, aby tworzyć klasę specjalnie dla wielu wartości zwracanych przez funkcję i nie powinny one być traktowane jako jeden obiekt, jeśli nie są ze sobą powiązane.

Aby być uczciwym, wklejony kod jest złym użytkiem pary.

+4

Po prostu grając tutaj adwokata diabła, ale czy naprawdę istnieje wiele przypadków, w których potrzebna jest metoda zwracająca wiele niepowiązanych wartości? Scenariusz, który widzę dość często, to metoda "TryParse" dla wbudowanych typów danych w języku C#. Tutaj musisz zwrócić 'bool' (bez względu na to, czy analizowanie zakończyło się powodzeniem) i, powiedzmy,' int' (wartość, jeśli analizowanie zakończyło się powodzeniem). Aby umożliwić drugą wartość zwracaną, używane jest słowo kluczowe "out". Myślę, że krotka może być tutaj fajna, ale nie jestem pewna, czy jest lepsza od deklarowania typu z 'bool' nazwanym' ParseSupcess' i typowym 'T' nazwanym' Value'. – devuxer

+1

@DanThMan: tak, chociaż nie są bez związku. Na przykład algorytm C++ 'equal_range' zwraca parę iteratorów. Mapy w C++ są równoważne zestawom 'pair '. Są inne przypadki, ale nie mogę o nich teraz myśleć. – rlbond

+1

DanThMan: Pochodząc z przeciwnego kierunku (język, który obsługuje wiele wartości zwracanych), można zapytać o to samo: czy kiedykolwiek potrzebujesz parametrów "out", jeśli masz wiele wartości zwracanych? Chcesz * zwrócić * 2 rzeczy, więc dlaczego wygląda tak, jakbym to przekazał? :-) – Ken

2

To jest po prostu prototypowy kod, który najprawdopodobniej został zmiażdżony i nigdy nie został poddany renowacji. Brak naprawy to tylko lenistwo.

Rzeczywistym zastosowaniem dla krotek jest ogólna funkcjonalność, która naprawdę nie obchodzi, jakie są części składowe, ale działa tylko na poziomie krotki.

+0

Josh: Jaki kod napiszesz, żeby to naprawić? – shahkalpesh

+0

Dokładnie to, co zaproponował, zadziała. Nie widząc więcej kontekstu, nie mogę tak naprawdę komentować. – Eclipse

16

Krotki są używane przez cały czas w Pythonie, gdzie są zintegrowane z językiem i bardzo użyteczne (pozwalają na wiele wartości zwracanych na początek).

Czasami naprawdę trzeba tylko sparować rzeczy i stworzyć prawdziwego, uczciwego wobec Boga, klasa jest przesadą. Jedną z drugiej strony, używanie krotek, kiedy naprawdę powinno się używać klasy, jest równie złym pomysłem, co odwrotnością.

+1

+1 powracanie wielu wartości to dla mnie duży powód. Klasa Pair jest prawdopodobnie dobrym kompromisem między zwracającym Object [] lub zupełnie nowym typem. –

1

Oczywistym przykładem jest para współrzędnych (lub potrójna). Etykiety nie mają znaczenia; używanie X i Y (i Z) to tylko konwencja. Ich ujednolicenie wyjaśnia, że ​​można je traktować w ten sam sposób.

+0

@JohnGibb - elementy w 'Tuple' mają różne znaczenie. Dlatego musisz osobno określać ich typy: 'Item1' może być łańcuchem, ale' Item2' jest liczbą całkowitą, itd. –

+0

W rzeczywistości 'x' i' y' faktycznie mają symetrię (zwykle w układzie współrzędnych traktujemy każdy wymiar identycznie), a zatem byłoby jeszcze więcej sensu, aby pójść o krok dalej i umieścić je w szeregu! –

8

Co jest warte, kod w OP jest bałagan nie dlatego, że używa krotek, ale dlatego, że wartości w krotce są zbyt słabo wpisane. Porównaj następujące:

List<Pair<Integer, Integer>> products_weak = blah1(); 
List<Pair<Product, Integer>> products_strong = blah2(); 

bym denerwować zbyt jeśli mój zespół dev zostały przechodząc wokół identyfikatory zamiast wystąpień klasy dookoła, ponieważ liczba całkowita może reprezentować nic.


Mając to na uwadze powyższe, krotki są niezwykle przydatne podczas korzystania z nich po prawej:

  • Krotki istnieją grupy ad hoc wartości razem. Są one z pewnością lepsze niż tworzenie nadmiernej liczby klas opakowania.
  • Przydatna alternatywa dla parametrów out/ref, gdy musisz zwrócić więcej niż jedną wartość z funkcji.

Jednak krotki w języku C# sprawiają, że moje oczy są pod wodą. Wiele języków, takich jak OCaml, Python, Haskell, F # i tak dalej, ma specjalną, zwięzłą składnię do definiowania krotek. Na przykład, f #, po Map module definiuje konstruktora następująco:

val of_list : ('key * 'a) list -> Map<'key,'a> 

może utworzyć przypadek mapie z:

(* val values : (int * string) list *) 
let values = 
    [1, "US"; 
    2, "Canada"; 
    3, "UK"; 
    4, "Australia"; 
    5, "Slovenia"] 

(* val dict : Map<int, string> *) 
let dict = Map.of_list values 

Kod equilvalent C# jest ridicuous:

var values = new Tuple<int, string>[] { 
    new Tuple<int, string>(1, "US"), 
    new Tuple<int, string>(2, "Canada"), 
    new Tuple<int, string>(3, "UK"), 
    new Tuple<int, string>(4, "Australia"), 
    new Tuple<int, string>(5, "Slovenia") 
    } 

var dict = new Dictionary<int, string>(values); 

Nie wierzę, że coś jest nie tak z krotkami w zasadzie, ale składnia C# jest zbyt kłopotliwa, aby uzyskać jak najlepsze wykorzystanie m.

+0

Możesz jednak trochę posprzątać. A co powiesz o klasie pomocnika? klasa statyczna Tuple {static Tuple Kompilacja (T0 t0, T1 t1); } Następnie możesz utworzyć krotkę taką jak: Tuple.Build (1, "US"); Exploit, że typy ogólne są wnioskowane dla funkcji. :) – jalf

+0

Nadal nie jest tak czysty jak w Pythonie czy ML/F #, ale przynajmniej unikasz konieczności określania ogólnych parametrów. – jalf

+0

Twój krotki kod mógłby tutaj zostać wykonany za pomocą słownika używającego inicjatorów słownika. (nowy słownik () {{1, "US"} {2, "Kanada"}}). ToTuple()? –

1

Wiele rzeczy już wspomniano, ale myślę, że należy również wspomnieć, że istnieją pewne style programowania, które różnią się od OOP i dla tych krotek są całkiem użyteczne.

Funkcjonalne języki programowania, takie jak np. Haskell, w ogóle nie mają klas.

10

Przykładowy kod ma kilka różnych zapachów:

  • wymyślania koła

Istnieje już krotka dostępne w ramach; struktura KeyValuePair. Jest to używane przez klasę Dictionary do przechowywania par, ale można z niej korzystać wszędzie, gdzie się zmieści. (Nie mówiąc, że pasuje w tym przypadku ...)

  • Making koło kwadrat

Jeśli masz listę par lepiej wykorzystywać struktury KeyValuePair niż klasy z tego samego celu , ponieważ powoduje to mniej alokacji pamięci.

  • Ukrywanie zamiar

Klasa o właściwościach wyraźnie pokazuje, co znaczy wartości, natomiast klasa Pair<int,int> nie mówi nic o tym, co reprezentują wartości (tylko, że są prawdopodobnie związane jakoś) . Aby kod był w zasadzie zrozumiały dla takiej listy, musiałbyś nadać liście bardzo uproszczoną nazwę, np. productIdAndQuantityPairs ...

4

To ponowne użycie kodu. Zamiast pisać jeszcze jedną klasę z dokładnie tą samą strukturą, jako 5 ostatnich klas, które stworzyliśmy ...klasa Tuple, i używaj tego, kiedy potrzebujesz krotki.

Jeśli jedynym znaczeniem klasy jest "zapisanie pary wartości", wtedy powiedziałbym, że używanie krotek jest oczywistym pomysłem. Powiedziałbym, że był to zapach kodu (tak samo jak nienawidzę tego terminu), jeśli zacząłeś wdrażać wiele identycznych klas, tak abyś mógł zmienić nazwę dwóch członków.

0

Jeśli robisz Schwartzian transform sortowanie według określonego klucza (co jest kosztowne wielokrotnie obliczenia), czy coś takiego, klasa wydaje się być trochę przesada:

val transformed = data map {x => (x.expensiveOperation, x)} 
val sortedTransformed = transformed sort {(x, y) => x._1 < y._1} 
val sorted = sortedTransformed map {case (_, x) => x} 

Posiadanie class DataAndKey lub cokolwiek, co wydaje się tu nieco zbyteczne.

Twój przykład nie był dobrym przykładem krotki, zgadzam się.

2

Scala ma typy wartości krotek, od 2-tek (par) po krotki z ponad 20 elementami. Zobacz First Steps to Scala (krok 9):

val pair = (99, "Luftballons") 
println(pair._1) 
println(pair._2) 

Krotki są przydatne, jeśli trzeba zapakować razem wartości dla niektórych stosunkowo ad hoc celu. Na przykład, jeśli masz funkcję, która musi zwrócić dwa dość niepowiązane obiekty, zamiast tworzyć nową klasę do przechowywania tych dwóch obiektów, zwracasz parę z funkcji.

Całkowicie zgadzam się z innymi plakatami, że krotki mogą zostać niewłaściwie użyte. Jeśli krotka ma jakąkolwiek semantykę ważną dla twojej aplikacji, powinieneś użyć odpowiedniej klasy.

Powiązane problemy