Apakah ada pola untuk cara yang lebih "alami" untuk menambahkan item ke koleksi? [Tutup]

13

Saya pikir cara paling umum untuk menambahkan sesuatu ke koleksi adalah dengan menggunakan beberapa Addmetode yang disediakan oleh koleksi:

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

dan sebenarnya tidak ada yang aneh tentang itu.

Namun saya heran mengapa kita tidak melakukannya dengan cara ini:

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

tampaknya lebih alami daripada metode pertama. Ini akan memiliki andantange bahwa ketika Itemkelas memiliki properti seperti Parent:

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

Anda dapat menjadikan setter pribadi. Dalam hal ini tentu saja Anda tidak dapat menggunakan metode ekstensi.

Tapi mungkin saya salah dan saya belum pernah melihat pola ini sebelumnya karena itu sangat jarang? Apakah Anda tahu ada pola seperti itu?

Dalam C#suatu metode ekstensi akan berguna untuk itu

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

Bagaimana dengan bahasa lain? Saya kira sebagian besar dari mereka Itemharus menyediakan ICollectionItemantarmuka , sebut saja antarmuka.


Perbarui-1

Saya telah memikirkannya sedikit lebih banyak dan pola ini akan sangat berguna misalnya jika Anda tidak ingin item ditambahkan ke beberapa koleksi.

ICollectableantarmuka uji :

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);
}

dan contoh implementasi:

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
}

pemakaian:

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
sumber
13
item.AddTo(items)misalkan Anda memiliki bahasa tanpa metode ekstensi: alami atau tidak, untuk mendukung addTo, setiap jenis memerlukan metode ini dan menyediakannya untuk setiap jenis koleksi yang mendukung penambahan. Itu seperti contoh terbaik untuk memperkenalkan dependensi antara semua yang pernah saya dengar: P - Saya pikir premis yang salah di sini sedang mencoba untuk memodelkan beberapa abstraksi pemrograman ke kehidupan 'nyata'. Itu sering salah.
stijn
1
@ t3chb0t Hehe, poin bagus. Saya bertanya-tanya apakah bahasa lisan pertama Anda memengaruhi konstruksi apa yang tampaknya lebih alami. Bagi saya, satu-satunya yang tampak alami sama sekali adalah add(item, collection), tapi itu bukan gaya OO yang baik.
Kilian Foth
3
@ t3chb0t Dalam bahasa lisan, Anda dapat membaca berbagai hal secara normal atau refleksif. "John, tolong tambahkan apel ini ke apa yang kamu bawa." vs "Apel harus ditambahkan ke apa yang Anda bawa, John." Dalam dunia OOP, refleksif tidak masuk akal. Anda tidak meminta sesuatu untuk dipengaruhi oleh objek lain secara umum. Yang paling dekat dengan ini dalam OOP adalah pola pengunjung . Ada alasan bahwa ini bukan norma.
Neil
3
Ini menempatkan haluan yang bagus di sekitar ide yang buruk .
Telastyn
3
@ t3chb0t Anda mungkin menemukan Kingdom of Nouns rant bacaan yang menarik.
paul

Jawaban:

51

Tidak, item.AddTo(items)itu tidak lebih alami. Saya pikir Anda mencampur ini dengan yang berikut:

t3chb0t.Add(item).To(items)

Anda benar karena items.Add(item)tidak terlalu dekat dengan bahasa Inggris alami. Tetapi Anda juga tidak mendengar item.AddTo(items)dalam bahasa Inggris alami, bukan? Biasanya ada seseorang yang seharusnya menambahkan item ke daftar. Baik itu ketika bekerja di supermarket atau saat memasak dan menambahkan bahan.

Dalam hal bahasa pemrograman, kami membuatnya sehingga daftar melakukan keduanya: menyimpan barang-barangnya dan bertanggung jawab untuk menambahkannya ke dirinya sendiri.

Masalah dengan pendekatan Anda adalah bahwa item harus sadar bahwa ada daftar. Tapi sebuah item bisa ada walaupun tidak ada daftar sama sekali, kan? Ini menunjukkan bahwa ia seharusnya tidak mengetahui daftar. Daftar tidak boleh muncul dalam kodenya sama sekali.

Namun daftar tidak ada tanpa item (setidaknya mereka tidak akan berguna). Oleh karena itu tidak masalah jika mereka tahu tentang barang-barang mereka - setidaknya dalam bentuk generik.

valenterri
sumber
4

@valenterry benar tentang masalah pemisahan masalah. Kelas seharusnya tidak tahu apa-apa tentang berbagai koleksi yang mungkin memuatnya. Anda tidak ingin harus mengubah kelas item setiap kali Anda membuat koleksi jenis baru.

Yang mengatakan ...

1. Bukan Java

Java tidak membantu Anda di sini, tetapi beberapa bahasa memiliki sintaksis yang lebih fleksibel dan dapat diperluas.

Di Scala, items.add (item) terlihat seperti ini:

  item :: items

Di mana :: adalah operator yang menambahkan item ke daftar. Sepertinya itu yang Anda inginkan. Ini sebenarnya adalah gula sintaksis

  items.::(item)

where :: adalah metode daftar Scala yang secara efektif setara dengan list.add Java . Jadi sementara itu terlihat di versi pertama seolah-olah item menambahkan dirinya sendiri ke item , sebenarnya item melakukan penambahan. Kedua versi tersebut adalah Scala yang valid

Trik ini dilakukan dalam dua langkah:

  1. Dalam Scala, operator benar-benar hanya metode. Jika Anda mengimpor daftar Java ke Scala, maka items.add (item) juga dapat berupa item tertulis tambahkan item . Dalam Scala, 1 + 2 sebenarnya adalah 1. + (2) . Dalam versi dengan spasi daripada titik dan tanda kurung, Scala melihat objek pertama, mencari metode yang cocok dengan kata berikutnya dan (jika metode tersebut mengambil parameter) melewati hal berikutnya (atau hal-hal) ke metode itu.
  2. Di Scala, operator yang berakhir di titik dua lebih asosiatif daripada kiri. Jadi ketika Scala melihat metode tersebut, ia mencari pemilik metode di sebelah kanan dan memasukkan nilai di sebelah kiri.

Jika Anda tidak nyaman dengan banyak operator dan menginginkan addTo Anda , Scala menawarkan opsi lain: konversi implisit. Ini membutuhkan dua langkah

  1. Buat kelas pembungkus yang disebut, katakanlah, ListItem yang mengambil item dalam konstruktornya dan memiliki metode addTo yang dapat menambahkan item itu ke daftar yang diberikan.
  2. Buat fungsi yang mengambil item sebagai parameter dan mengembalikan ListItem yang mengandung item itu . Tandai sebagai tersirat .

Jika konversi implisit diaktifkan dan Scala melihat

  item addTo items

atau

  item.addTo(items)

dan item tidak memiliki addTo , item akan mencari ruang lingkup saat ini untuk setiap fungsi konversi implisit. Jika salah satu fungsi konversi mengembalikan tipe yang memang memiliki metode addTo , ini terjadi:

  ListItem.(item).addTo(items)

Sekarang, Anda mungkin menemukan konversi implisit lebih sesuai dengan selera Anda, tetapi menambah bahaya, karena kode dapat melakukan hal-hal yang tidak terduga jika Anda tidak mengetahui dengan jelas semua konversi implisit dalam konteks tertentu. Inilah sebabnya mengapa fitur ini tidak lagi diaktifkan secara default di Scala. (Namun, sementara Anda dapat memilih untuk mengaktifkannya atau tidak dalam kode Anda sendiri, Anda tidak dapat melakukan apa-apa tentang statusnya di pustaka orang lain. Ini naga).

Operator yang diakhiri usus besar lebih buruk tetapi tidak menimbulkan ancaman.

Kedua pendekatan mempertahankan prinsip pemisahan keprihatinan. Anda dapat membuatnya terlihat seolah-olah barang yang tahu tentang koleksi, tetapi pekerjaan sebenarnya dilakukan di tempat lain. Bahasa lain apa pun yang ingin memberi Anda fitur ini harus setidaknya sama hati-hati.

2. Jawa

Di Jawa, di mana sintaksisnya tidak bisa diperluas oleh Anda, yang terbaik yang bisa Anda lakukan adalah membuat semacam kelas pembungkus / dekorator yang tahu tentang daftar. Di domain tempat item Anda ditempatkan dalam daftar, Anda bisa membungkusnya di dalamnya. Ketika mereka meninggalkan domain itu, keluarkan mereka. Mungkin pola pengunjung terdekat.

3. tl; dr

Scala, seperti beberapa bahasa lain, dapat membantu Anda dengan sintaksis yang lebih alami. Java hanya bisa menawarkan pola barok yang jelek.

itu otaknya
sumber
2

Dengan metode ekstensi Anda, Anda masih harus memanggil Tambah () sehingga tidak menambahkan apa pun yang berguna untuk kode Anda. Kecuali jika metode addto Anda melakukan beberapa pemrosesan lain, tidak ada alasan untuk membuatnya ada. Dan menulis kode yang tidak memiliki tujuan fungsional buruk untuk keterbacaan dan pemeliharaan.

Jika Anda membiarkannya tidak umum, masalahnya menjadi lebih jelas. Anda sekarang memiliki item yang perlu diketahui tentang berbagai jenis koleksi yang ada. Itu melanggar prinsip tanggung jawab tunggal. Tidak hanya item pemegang untuk data itu, item ini juga memanipulasi koleksi. Itulah sebabnya kami meninggalkan tanggung jawab untuk menambahkan item ke koleksi hingga potongan kode yang memakan keduanya.

kontrol
sumber
Jika suatu kerangka kerja untuk mengenali berbagai jenis referensi objek (misalnya membedakan antara yang meringkas kepemilikan dan yang tidak), mungkin masuk akal untuk memiliki sarana dengan mana referensi yang merangkum kepemilikan dapat melepaskan kepemilikan ke koleksi yang mampu menerimanya. Jika setiap hal terbatas pada memiliki satu pemilik, mentransfer kepemilikan melalui thing.giveTo(collection)[dan mengharuskan collectionuntuk menerapkan antarmuka "acceptOwnership"] akan lebih masuk akal daripada collectionmemasukkan metode yang merebut kepemilikan. Tentu saja ...
supercat
... sebagian besar hal di Jawa tidak memiliki konsep kepemilikan, dan referensi objek dapat disimpan ke dalam koleksi tanpa melibatkan objek yang diidentifikasi.
supercat
1

Saya pikir Anda terobsesi dengan kata "tambah". Coba cari kata alternatif saja, seperti "kumpulkan", yang akan memberi Anda sintaks yang lebih ramah bahasa Inggris tanpa perubahan pola:

items.collect(item);

Metode di atas persis sama dengan items.add(item);, dengan satu-satunya perbedaan adalah pilihan kata yang berbeda, dan mengalir dengan sangat baik dan "alami" dalam bahasa Inggris.

Terkadang, hanya penamaan ulang variabel, metode, kelas, dll. Akan mencegah perpajakan desain ulang pola.

Beristirahat dengan damai
sumber
collectkadang-kadang digunakan sebagai nama untuk metode yang mengurangi koleksi item menjadi semacam hasil tunggal (yang juga bisa menjadi koleksi). Konvensi ini diadopsi oleh Java Steam API , yang membuat penggunaan nama dalam konteks ini sangat populer. Saya belum pernah melihat kata yang digunakan dalam konteks yang Anda sebutkan dalam jawaban Anda. Saya lebih suka tetap dengan addatauput
toniedzwiedz
@toniedzwiedz, Jadi pertimbangkan pushatau enqueue. Intinya bukan nama contoh spesifik, tetapi fakta bahwa nama yang berbeda mungkin lebih dekat dengan keinginan OP untuk tata bahasa Inggris-esque.
Brian S
1

Meskipun tidak ada pola seperti itu untuk kasus umum (seperti yang dijelaskan oleh orang lain), konteks di mana pola yang sama memiliki manfaatnya adalah asosiasi dua arah dua arah dalam ORM.

Dalam konteks ini Anda akan memiliki dua entitas, katakanlah Parentdan Child. Orang tua dapat memiliki banyak anak, tetapi setiap anak milik satu orang tua. Jika aplikasi mengharuskan asosiasi dapat dinavigasi dari kedua ujungnya (dua arah), Anda akan memiliki List<Child> childrendi kelas induk dan Parent parentdi kelas anak.

Asosiasi harus selalu bersifat timbal balik tentu saja. Solusi ORM biasanya memberlakukan ini pada entitas yang mereka muat. Tetapi ketika aplikasi memanipulasi entitas (baik yang bertahan atau yang baru), ia harus menegakkan aturan yang sama. Dalam pengalaman saya, Anda paling sering menemukan Parent.addChild(child)metode yang memanggil Child.setParent(parent), yang tidak untuk penggunaan umum. Dalam yang terakhir Anda biasanya akan menemukan cek yang sama seperti yang telah Anda terapkan dalam contoh Anda. Namun rute lain juga dapat diterapkan: Child.addTo(parent)panggilan mana Parent.addChild(child). Rute mana yang terbaik berbeda dari situasi ke situasi.

Maarten Winkels
sumber
1

Di Jawa, koleksi khas tidak memegang hal --Itu memegang referensi untuk hal-hal, atau lebih tepatnya salinan dari referensi untuk hal-hal. Sintaks dot-anggota, sebaliknya, umumnya digunakan untuk bertindak atas hal yang diidentifikasi oleh referensi, bukan pada referensi itu sendiri.

Sementara menempatkan apel dalam mangkuk akan mengubah beberapa aspek apel (misalnya lokasinya), menempatkan selembar kertas yang bertuliskan "objek # 29521" ke dalam mangkuk tidak akan mengubah apel dengan cara apa pun meskipun itu terjadi. objek # 29521. Lebih lanjut, karena koleksi menyimpan salinan referensi, tidak ada Jawa yang setara dengan menempatkan selembar kertas yang mengatakan "objek # 29521" ke dalam mangkuk. Sebagai gantinya, seseorang menyalin nomor # 29521 ke selembar kertas baru, dan menunjukkan (tidak memberikan) itu ke mangkuk, yang akan mereka buat salinannya sendiri, meninggalkan yang asli tidak terpengaruh.

Memang, karena referensi objek ("slip kertas") di Jawa dapat diamati secara pasif, baik referensi, maupun objek yang diidentifikasi, tidak memiliki cara untuk mengetahui apa yang sedang dilakukan dengan mereka. Ada beberapa jenis koleksi yang, alih-alih hanya memegang setumpuk referensi ke objek yang mungkin tidak tahu apa-apa tentang keberadaan koleksi, bukannya membangun hubungan dua arah dengan objek yang diringkas di dalamnya. Dengan beberapa koleksi seperti itu, mungkin masuk akal untuk meminta metode untuk menambahkan dirinya ke koleksi (terutama jika suatu objek hanya mampu berada di satu koleksi seperti itu pada suatu waktu). Namun, secara umum, sebagian besar koleksi tidak cocok dengan pola itu, dan dapat menerima referensi ke objek tanpa keterlibatan apa pun pada bagian objek yang diidentifikasi oleh referensi tersebut.

supercat
sumber
posting ini agak sulit dibaca (dinding teks). Maukah Anda mengeditnya menjadi bentuk yang lebih baik?
nyamuk
@gnat: Apakah itu lebih baik?
supercat
1
@gnat: Maaf - gangguan jaringan menyebabkan upaya saya untuk mengirim hasil edit gagal. Apakah Anda menyukainya lebih baik sekarang?
supercat
-2

item.AddTo(items)tidak digunakan karena pasif. Cf "Item ditambahkan ke daftar" dan "ruangan dilukis oleh dekorator".

Konstruksi pasif baik-baik saja, tetapi, setidaknya dalam bahasa Inggris, kami cenderung lebih suka konstruksi aktif.

Dalam pemrograman OO, pardigmanya menonjol bagi aktor, bukan aktor yang ditindaklanjuti, jadi kami memilikinya items.Add(item). Daftar adalah hal yang melakukan sesuatu. Ini adalah aktor, jadi ini adalah cara yang lebih alami.

Matt Ellen
sumber
Itu tergantung pada di mana Anda berdiri ;-) - teori relativitas - Anda bisa mengatakan hal yang sama tentang list.Add(item) suatu item sedang ditambahkan ke daftar atau daftar menambahkan item atau tentang item.AddTo(list) item menambahkan dirinya ke daftar atau saya menambahkan item ke daftar atau seperti Anda telah mengatakan item ditambahkan ke daftar - semuanya benar. Jadi pertanyaannya adalah siapa aktornya dan mengapa? Item juga merupakan aktor dan ingin bergabung dengan grup (daftar) bukan grup yang ingin memiliki item ini ;-) item tersebut berperan aktif dalam grup.
t3chb0t
Aktor adalah hal yang melakukan hal yang aktif. "Menginginkan" adalah hal yang pasif. Dalam pemrograman, itu adalah daftar yang berubah ketika item ditambahkan ke dalamnya, item tidak boleh berubah sama sekali.
Matt Ellen
... meskipun sering suka ketika memiliki Parentproperti. Memang bahasa Inggris bukan bahasa asli tetapi sejauh yang saya tahu inginkan tidak pasif kecuali saya ingin atau dia ingin saya melakukan sesuatu ; -]
t3chb0t
Tindakan apa yang Anda lakukan ketika Anda menginginkan sesuatu? Itu poin saya. Ini tidak harus secara tata bahasa pasif tetapi semantik itu. Atribut orang tua WRT, tentu saja itu pengecualian, tetapi semua heuristik memilikinya.
Matt Ellen
Tetapi List<T>(setidaknya yang ditemukan di System.Collections.Generic) tidak memperbarui Parentproperti sama sekali. Bagaimana mungkin, jika itu seharusnya generik? Biasanya tanggung jawab wadah untuk menambahkan isinya.
Arturo Torres Sánchez