Czy istnieje wzorzec bardziej „naturalnego” sposobu dodawania przedmiotów do kolekcji? [Zamknięte]

13

Myślę, że najczęstszym sposobem dodawania czegoś do kolekcji jest użycie Addmetody zapewnianej przez kolekcję:

class Item {}    
var items = new List<Item>();
items.Add(new Item());

i nie ma w tym nic niezwykłego.

Zastanawiam się jednak, dlaczego nie zrobimy tego w ten sposób:

var item = new Item();
item.AddTo(items);

wydaje się, że jest to bardziej naturalne niż pierwsza metoda. Oznaczałoby to, że gdy Itemklasa ma właściwość taką jak Parent:

class Item 
{
    public object Parent { get; private set; }
} 

możesz ustawić setera jako prywatny. W takim przypadku oczywiście nie można użyć metody rozszerzenia.

Ale może się mylę i nigdy wcześniej nie widziałem tego wzoru, ponieważ jest tak rzadki? Czy wiesz, czy istnieje taki wzór?

W C#rozszerzeniu metoda byłaby przydatna, że

public static T AddTo(this T item, IList<T> list)
{
    list.Add(item);
    return item;
}

Co powiesz na inne języki? Myślę, że w większości z nich Itemklasa musiała zapewnić ICollectionIteminterfejs nazwijmy to .


Aktualizacja-1

Myślałem o tym trochę więcej i ten wzór byłby bardzo przydatny, na przykład, jeśli nie chcesz, aby element był dodawany do wielu kolekcji.

ICollectableinterfejs testowy :

interface ICollectable<T>
{
    // Gets a value indicating whether the item can be in multiple collections.
    bool CanBeInMultipleCollections { get; }

    // Gets a list of item's owners.
    List<ICollection<T>> Owners { get; }

    // Adds the item to a collection.
    ICollectable<T> AddTo(ICollection<T> collection);

    // Removes the item from a collection.
    ICollectable<T> RemoveFrom(ICollection<T> collection);

    // Checks if the item is in a collection.
    bool IsIn(ICollection<T> collection);
}

i przykładowa implementacja:

class NodeList : List<NodeList>, ICollectable<NodeList>
{
    #region ICollectable implementation.

    List<ICollection<NodeList>> owners = new List<ICollection<NodeList>>();

    public bool CanBeInMultipleCollections
    {
        get { return false; }
    }

    public ICollectable<NodeList> AddTo(ICollection<NodeList> collection)
    {
        if (IsIn(collection))
        {
            throw new InvalidOperationException("Item already added.");
        }

        if (!CanBeInMultipleCollections)
        {
            bool isInAnotherCollection = owners.Count > 0;
            if (isInAnotherCollection)
            {
                throw new InvalidOperationException("Item is already in another collection.");
            }
        }
        collection.Add(this);
        owners.Add(collection);
        return this;
    }

    public ICollectable<NodeList> RemoveFrom(ICollection<NodeList> collection)
    {
        owners.Remove(collection);
        collection.Remove(this);
        return this;
    }

    public List<ICollection<NodeList>> Owners
    {
        get { return owners; }
    }

    public bool IsIn(ICollection<NodeList> collection)
    {
        return collection.Contains(this);
    }

    #endregion
}

stosowanie:

var rootNodeList1 = new NodeList();
var rootNodeList2 = new NodeList();

var subNodeList4 = new NodeList().AddTo(rootNodeList1);

// Let's move it to the other root node:
subNodeList4.RemoveFrom(rootNodeList1).AddTo(rootNodeList2);

// Let's try to add it to the first root node again... 
// and it will throw an exception because it can be in only one collection at the same time.
subNodeList4.AddTo(rootNodeList1);
t3chb0t
źródło
13
item.AddTo(items)załóżmy, że masz język bez metod rozszerzenia: naturalny lub nie, do obsługi addTo każdy typ potrzebuje tej metody i udostępnia ją dla każdego typu kolekcji obsługującej dołączanie. To jest najlepszy przykład wprowadzenia zależności między wszystkim , co kiedykolwiek słyszałem: P - Myślę, że fałszywą przesłanką jest próba modelowania abstrakcji programowej do „prawdziwego” życia. To często idzie nie tak.
stijn
1
@ t3chb0t Hehe, dobry punkt. Zastanawiałem się, czy twój pierwszy język mówiony wpływa na to, który konstrukt wydaje się bardziej naturalny. Dla mnie jedynym, który wydaje się w ogóle naturalny, byłby add(item, collection), ale to nie jest dobry styl OO.
Kilian Foth,
3
@ t3chb0t W języku mówionym możesz czytać rzeczy normalnie lub odruchowo. „John, dodaj to jabłko do tego, co nosisz”. vs „Jabłko powinno zostać dodane do tego, co nosisz, John”. W świecie OOP refleksyjny nie ma większego sensu. Mówiąc ogólnie, nie prosisz o coś innego. Najbliżej tego w OOP jest wzór odwiedzin . Jest jednak powód, dla którego nie jest to normą.
Neil
3
To stawia dobry ukłon wokół złego pomysłu .
Telastyn
3
@ t3chb0t Być może królestwo rzeczowników stanowi ciekawą lekturę.
Paul

Odpowiedzi:

51

Nie, item.AddTo(items)to nie jest bardziej naturalne. Myślę, że pomieszacie to z następującymi:

t3chb0t.Add(item).To(items)

Masz rację, ponieważ items.Add(item)nie jest bardzo zbliżony do naturalnego języka angielskiego. Ale ty też nie słyszysz item.AddTo(items)w naturalnym języku angielskim, prawda? Zwykle jest ktoś, kto powinien dodać element do listy. Czy to podczas pracy w supermarkecie, czy podczas gotowania i dodawania składników.

W przypadku języków programowania stworzyliśmy go tak, aby lista zawierała zarówno przechowywanie swoich elementów, jak i odpowiedzialność za ich dodawanie do siebie.

Problem z twoim podejściem polega na tym, że element musi wiedzieć, że istnieje lista. Ale przedmiot mógłby istnieć, nawet gdyby w ogóle nie było list, prawda? To pokazuje, że nie powinien być świadomy list. Listy w ogóle nie powinny występować w kodzie.

Listy nie istnieją jednak bez przedmiotów (przynajmniej byłyby bezużyteczne). Dlatego dobrze, jeśli wiedzą o swoich przedmiotach - przynajmniej w formie ogólnej.

walenteria
źródło
4

@valenterry ma rację co do problemu separacji problemów. Klasy nie powinny wiedzieć nic o różnych kolekcjach, które mogą je zawierać. Nie będziesz musiał zmieniać klasy przedmiotów za każdym razem, gdy tworzysz nowy rodzaj kolekcji.

To mówi...

1. Nie Java

Java nie jest tutaj pomocna, ale niektóre języki mają bardziej elastyczną, rozszerzalną składnię.

W Scali item.add (item) wygląda następująco:

  item :: items

Where :: jest operatorem, który dodaje elementy do list. To wydaje się bardzo, czego chcesz. W rzeczywistości jest to cukier syntaktyczny

  items.::(item)

gdzie :: to metoda listy Scala, która jest faktycznie równoważna z list.add Java . Tak więc, chociaż wygląda na to, że w pierwszej wersji element dodaje się do elementów , tak naprawdę elementy dodają. Obie wersje są prawidłowe Scala

Ta sztuczka odbywa się w dwóch krokach:

  1. W Scali operatorzy to tak naprawdę tylko metody. Jeśli importujesz listy Java do Scali, wtedy items.add (item) można również napisać items add item . W Scali 1 + 2 to tak naprawdę 1. + (2) . W wersji ze spacjami zamiast kropek i nawiasów Scala widzi pierwszy obiekt, szuka metody pasującej do następnego słowa i (jeśli metoda przyjmuje parametry) przekazuje następną rzecz (lub rzeczy) do tej metody.
  2. W Scali operatory kończące się dwukropkiem są raczej powiązane z prawą, a nie lewą stroną. Kiedy więc Scala zobaczy metodę, szuka właściciela metody po prawej stronie i podaje wartość po lewej.

Jeśli nie czujesz się komfortowo z wieloma operatorami i chcesz mieć swój dodatek , Scala oferuje inną opcję: konwersje niejawne. Wymaga to dwóch kroków

  1. Utwórz klasę opakowania o nazwie powiedzmy ListItem, która pobiera element do swojego konstruktora i ma metodę addTo , która może dodać ten element do danej listy.
  2. Utwórz funkcję, która przyjmuje element jako parametr i zwraca ListItem zawierający ten element . Oznacz to jako dorozumiane .

Jeśli włączone są niejawne konwersje i Scala widzi

  item addTo items

lub

  item.addTo(items)

a element nie ma addTo , przeszuka bieżący zakres pod kątem niejawnych funkcji konwersji. Jeżeli którykolwiek z funkcji konwersji zwraca typ, który robi mieć addto sposób, tak się dzieje:

  ListItem.(item).addTo(items)

Teraz może się okazać, że ukryte konwersje kierują się bardziej do gustu, ale dodaje to niebezpieczeństwa, ponieważ kod może robić nieoczekiwane rzeczy, jeśli nie jesteś wyraźnie świadomy wszystkich ukrytych konwersji w danym kontekście. Dlatego ta funkcja nie jest już domyślnie włączona w Scali. (Chociaż możesz jednak zdecydować, czy włączyć go we własnym kodzie, nie możesz nic zrobić z jego statusem w bibliotekach innych osób. Oto smoki).

Operatory zakończone dwukropkiem są brzydsze, ale nie stanowią zagrożenia.

Oba podejścia zachowują zasadę rozdzielenia obaw. Możesz sprawić, by wyglądał tak, jakby przedmiot wiedział o kolekcji, ale praca jest faktycznie wykonywana gdzie indziej. Każdy inny język, który chce udostępnić tę funkcję, powinien być co najmniej tak ostrożny.

2. Java

W Javie, gdzie składnia nie jest przez ciebie rozszerzalna, najlepsze, co możesz zrobić, to stworzyć pewnego rodzaju klasę opakowania / dekoratora, która zna listy. W domenie, w której twoje przedmioty są umieszczone na listach, możesz je w to owinąć. Kiedy opuszczą tę domenę, wyjmij je. Może wzór odwiedzin jest najbliższy.

3. tl; dr

Scala, podobnie jak niektóre inne języki, może pomóc ci w bardziej naturalnej składni. Java może zaoferować tylko brzydkie, barokowe wzory.

itsbruce
źródło
2

W metodzie rozszerzenia nadal musisz wywoływać funkcję Add (), aby nie dodawała niczego przydatnego do kodu. Chyba że twoja metoda addto wykonała jakieś inne przetwarzanie, nie ma powodu, aby istniała. A pisanie kodu, który nie ma celu funkcjonalnego, szkodzi czytelności i łatwości konserwacji.

Jeśli pozostawisz to nie rodzajowe, problemy staną się jeszcze bardziej widoczne. Masz teraz przedmiot, który musi wiedzieć o różnych rodzajach kolekcji, w których może się znajdować. To narusza zasadę pojedynczej odpowiedzialności. Element jest nie tylko posiadaczem danych, ale także manipuluje kolekcjami. Dlatego ponosimy odpowiedzialność za dodawanie elementów do kolekcji aż do fragmentu kodu, który zużywa oba z nich.

kontrola
źródło
Jeśli struktura ma rozpoznać różne rodzaje odwołań do obiektów (np. Rozróżnienie między tymi, które zawierają własność, a tymi, które tego nie robią), sensowne może być posiadanie środków, dzięki którym odniesienie, które ujmuje własność, może zrzec się prawa własności do kolekcji, która była zdolny do otrzymania. Jeśli każda rzecz ogranicza się do posiadania jednego właściciela, przeniesienie własności za pośrednictwem thing.giveTo(collection)[i wymaganie collectionwdrożenia interfejsu „acceptOwnership”] byłoby bardziej sensowne niż uwzględnienie collectionmetody przejęcia własności. Oczywiście ...
supercat
... większość rzeczy w Javie nie ma pojęcia własności, a odwołanie do obiektu może być przechowywane w kolekcji bez angażowania zidentyfikowanego obiektu.
supercat
1

Myślę, że masz obsesję na punkcie słowa „dodaj”. Zamiast tego spróbuj znaleźć alternatywne słowo, na przykład „zbieraj”, co zapewni bardziej przyjazną dla języka angielskiego składnię bez zmiany wzorca:

items.collect(item);

Powyższa metoda jest dokładnie taka sama items.add(item);, z tą różnicą, że różni się wyborem słów, i płynie bardzo ładnie i „naturalnie” po angielsku.

Czasami sama zmiana nazw zmiennych, metod, klas itp. Zapobiegnie opodatkowaniu ponownego zaprojektowania wzorca.

Spoczywaj w pokoju
źródło
collectjest czasem używany jako nazwa metod, które redukują zbiór przedmiotów do pewnego rodzaju pojedynczego wyniku (który może być również zbiorem). Konwencja ta została przyjęta przez Java Steam API , dzięki czemu użycie nazwy w tym kontekście jest niezwykle popularne. Nigdy nie widziałem tego słowa w kontekście, o którym wspominasz w swojej odpowiedzi. Wolałbym trzymać się addlubput
toniedzwiedz
@toniedzwiedz, więc zastanów się pushlub enqueue. Nie chodzi o konkretną przykładową nazwę, ale o fakt, że inna nazwa może być bliższa chęci OP do gramatyki angielskiej.
Brian S
1

Chociaż nie ma takiego wzorca dla ogólnego przypadku (jak wyjaśniono przez innych), kontekstem, w którym podobny wzorzec ma swoje zalety, jest dwukierunkowe połączenie jeden-do-wielu w ORM.

W tym kontekście miałbyś dwa byty, powiedzmy Parenti Child. Rodzic może mieć wiele dzieci, ale każde dziecko należy do jednego rodzica. Jeśli aplikacja wymaga, aby powiązanie było nawigowalne z obu końców (dwukierunkowe), miałbyś List<Child> childrenw klasie nadrzędnej i Parent parentw klasie podrzędnej.

Stowarzyszenie powinno być zawsze wzajemne. Rozwiązania ORM zazwyczaj egzekwują to w przypadku ładowanych przez siebie jednostek. Ale gdy aplikacja manipuluje jednostką (utrwaloną lub nową), musi egzekwować te same reguły. Z mojego doświadczenia Parent.addChild(child)wynika , że najczęściej można znaleźć metodę wywołującą Child.setParent(parent), która nie jest do użytku publicznego. W tym ostatnim zwykle znajdziesz te same kontrole, które wprowadziłeś w swoim przykładzie. Inną trasę można jednak również wdrożyć: Child.addTo(parent)które połączenia Parent.addChild(child). Która trasa najlepiej różni się w zależności od sytuacji.

Maarten Winkels
źródło
1

W Javie, typowy zbiór nie posiada rzeczy --it trzyma odniesień do rzeczy, a dokładniej kopii z odniesieniami do rzeczy. Natomiast składnia składowa kropkowa jest zwykle stosowana do działania na rzecz zidentyfikowaną przez odwołanie, a nie na samym odwołaniu.

Chociaż umieszczenie jabłka w misce zmieniłoby niektóre aspekty jabłka (np. Jego lokalizację), umieszczenie w misce kartki papieru z napisem „obiekt # 29521” nie zmieniłoby jabłka w żaden sposób, nawet gdyby tak się stało obiekt # 29521. Ponadto, ponieważ w kolekcjach znajdują się kopie referencji, nie ma odpowiednika w języku Java do umieszczania w misce kartki papieru z napisem „obiekt # 29521”. Zamiast tego kopiuje się numer # 29521 na nowy kawałek papieru i pokazuje (nie daje) go do miski, która zrobi własną kopię, pozostawiając oryginał bez zmian.

Rzeczywiście, ponieważ odniesienia do obiektów („kartki papieru”) w Javie nie mogą być biernie obserwowane, ani odniesienia, ani obiekty zidentyfikowane w ten sposób, nie mają żadnego sposobu, aby wiedzieć, co się z nimi dzieje. Istnieje kilka rodzajów kolekcji, które zamiast zawierać jedynie zbiór odniesień do obiektów, które mogą nic nie wiedzieć o istnieniu kolekcji, zamiast tego ustanawiają dwukierunkową relację z obiektami w nich zawartymi. W przypadku niektórych takich kolekcji sensowne może być poproszenie o metodę dodania się do kolekcji (zwłaszcza jeśli obiekt może znajdować się tylko w jednej takiej kolekcji naraz). Zasadniczo jednak większość kolekcji nie pasuje do tego wzorca i może akceptować odniesienia do obiektów bez żadnego udziału ze strony obiektów identyfikowanych przez te odniesienia.

supercat
źródło
ten post jest raczej trudny do odczytania (ściana tekstu). Czy mógłbyś edytować go w lepszym kształcie?
komara
@gnat: Czy to lepiej?
supercat
1
@gnat: Przepraszamy - czkawka sieciowa spowodowała, że ​​moja próba opublikowania edycji nie powiodła się. Czy lubisz to teraz lepiej?
supercat
-2

item.AddTo(items)nie jest używany, ponieważ jest pasywny. Por. „Element został dodany do listy” i „pokój jest namalowany przez dekoratora”.

Konstrukcje pasywne są w porządku, ale przynajmniej w języku angielskim preferujemy konstrukcje aktywne.

W programowaniu OO, ułaskawienie nadaje znaczenie aktorom, a nie aktorom, więc mamy items.Add(item). Ta lista coś robi. To aktor, więc jest to bardziej naturalny sposób.

Matt Ellen
źródło
Zależy to od tego, gdzie stoisz ;-) - teoria względności - możesz powiedzieć to samo o list.Add(item) dodawaniu elementu do listy lub o liście dodaje element lub o item.AddTo(list) tym, że element dodaje się do listy, lub dodam element do listy lub jak powiedziałeś, element został dodany do listy - wszystkie są poprawne. Pytanie brzmi, kto jest aktorem i dlaczego? Element jest także aktorem i chce dołączyć do grupy (listy), a nie grupa chce mieć ten element ;-) element działa aktywnie, aby należeć do grupy.
t3chb0t
Aktor robi rzeczy aktywne. „Chcieć” jest pasywną rzeczą. W programowaniu lista zmienia się po dodaniu do niej elementu, element nie powinien się w ogóle zmieniać.
Matt Ellen,
... choć często lubi, gdy ma Parentwłaściwość. Trzeba przyznać, że angielski nie jest językiem ojczystym, ale o ile wiem, chęć nie jest pasywna, chyba że mnie chcę lub chce, żebym coś zrobił ; -]
t3chb0t
Jakie działania wykonujesz, gdy czegoś chcesz? O to mi chodzi. Niekoniecznie jest to gramatyka pasywna, ale jest semantyczna. Atrybuty nadrzędne WRT, na pewno jest to wyjątek, ale mają je wszystkie heurystyki.
Matt Ellen,
Ale List<T>(przynajmniej ta znaleziona w System.Collections.Generic) nie aktualizuje żadnej Parentwłaściwości. Jak to możliwe, jeśli ma być ogólne? Zazwyczaj to pojemnik jest odpowiedzialny za dodanie jego zawartości.
Arturo Torres Sánchez