Cara mendesain daftar acara yang tidak terkait secara berurutan
Ini adalah sedikit contoh yang ditemukan tetapi saya pikir itu paling menggambarkan pertanyaan saya: Katakanlah saya membuat API acara replay catur. Katakanlah saya memiliki banyak "peristiwa" berbeda yang ingin saya pantau, dalam urutan tertentu. Berikut ini beberapa contohnya:
Peristiwa pindah - ini berisi kotak
previous
dannew
.Peristiwa pengatur waktu - ini berisi
timestamp
pengatur waktu yang diubah-ubah antara pemainAcara pesan obrolan - ini berisi
player ID
,message
dantime sent
... dll. Intinya adalah bahwa model data untuk setiap peristiwa sangat berbeda - tidak banyak antarmuka yang umum.
Saya ingin merancang API yang dapat menyimpan dan mengekspos pada dasarnya List<Event>
klien yang dapat memilih untuk memproses berbagai peristiwa ini sesuai keinginan. Kami tidak tahu apa yang akan dilakukan klien dengan informasi ini: Mungkin satu klien mungkin perlu melakukan analisis teks pada ChatMessageEvent
s, dan klien dapat menggunakan dan memutar ulang peristiwa ini di UI. Tantangannya adalah bahwa pengurutan antara peristiwa harus dipertahankan, jadi saya tidak dapat memisahkan dengan metode seperti getMoveEvents
dan getTimerEvents
karena TimerEvent
dapat terjadi antara peristiwa pemindahan dan klien mungkin memerlukan informasi tersebut.
Saya dapat mengekspos pengunjung untuk mengizinkan klien menangani setiap jenis acara secara berbeda dalam daftar, tetapi saya bertanya-tanya apakah ada cara yang lebih baik untuk menangani situasi seperti ini.
Sunting: Saya ingin merancang ini dengan satu prioritas utama: memberi klien cara yang mudah dan fleksibel untuk mengulang melalui acara ini. Dalam skenario yang ideal, saya akan membayangkan penangan penulisan pengguna akhir ke jenis acara yang mereka pedulikan, dan kemudian dapat mengulang tanpa casting berdasarkan jenis runtime.
Jawaban
Saya mendapat kesan kuat bahwa Anda terlalu memikirkan hal ini.
Tantangannya adalah bahwa pengurutan antara acara harus dipertahankan, jadi saya tidak dapat memisahkan dengan metode seperti getMoveEvents dan getTimerEvents
Maka jangan menawarkan metode seperti itu di API Anda. Biarkan klien memfilter acara yang mereka butuhkan, dan jangan menerapkan apa pun di API Anda yang dapat menjadi rawan kesalahan.
Saya dapat mengekspos pengunjung untuk mengizinkan klien menangani setiap jenis acara secara berbeda dalam daftar
Ini terdengar terlalu direkayasa. Anda menggambarkan persyaratan tersebut sebagai mendapatkan sesuatu seperti List<Event>
, yang berisi peristiwa yang direkam. Untuk ini, metode sederhana List<Event> getEvents()
sudah cukup (mungkin IEnumerable<Event>
cukup). Untuk alasan efisiensi, mungkin perlu menawarkan beberapa metode untuk membatasi hasil yang ditetapkan pada kondisi tertentu.
tapi saya bertanya-tanya apakah ada cara yang lebih baik untuk menangani situasi seperti ini
Meminta pendekatan yang "lebih baik" (atau "terbaik", atau "benar") terlalu tidak spesifik ketika Anda tidak mengetahui kriteria apa pun untuk apa yang sebenarnya Anda maksud dengan "lebih baik". Tetapi bagaimana menemukan kriteria untuk apa yang "lebih baik"? Satu-satunya cara tepercaya yang saya ketahui untuk masalah ini adalah:
Tentukan beberapa kasus penggunaan umum untuk API Anda!
Lakukan ini dalam kode. Tuliskan fungsi singkat yang mencoba menggunakan API Anda, memecahkan masalah nyata yang Anda tahu pasti akan dihadapi klien (bahkan jika API tidak ada atau belum diimplementasikan).
Ini mungkin ternyata klien akan membutuhkan sesuatu seperti properti untuk membedakan jenis acara. Mungkin ternyata klien membutuhkan sesuatu untuk hanya mendapatkan peristiwa dari satu jam terakhir, atau 100 peristiwa terakhir, karena memberikan salinan lengkap semua peristiwa sebelumnya mungkin tidak cukup efisien. Mungkin ternyata klien perlu mendapatkan pemberitahuan setiap kali acara baru dibuat.
Anda hanya akan dapat memutuskan ini ketika Anda mengembangkan gagasan yang jelas tentang konteks di mana API Anda akan digunakan.
Jika Anda menambahkan beberapa kode ke fungsi ini yang memverifikasi hasil API, dan menempatkan kode ini ke dalam konteks kerangka pengujian unit, maka Anda melakukan "Pengembangan yang Didorong Uji"
Tetapi bahkan jika Anda tidak ingin menggunakan TDD atau tidak menyukai TDD, yang terbaik adalah melakukan pendekatan ini dari perspektif klien .
Jangan menambahkan apa pun ke API Anda di mana Anda ragu apakah akan pernah ada kasus penggunaannya. Kemungkinan besar tidak ada orang yang membutuhkan fungsi seperti itu.
Jika Anda tidak cukup tahu tentang kasus penggunaan API untuk menggunakan pendekatan ini, Anda mungkin akan melakukan beberapa analisis persyaratan terlebih dahulu - dan itu adalah sesuatu yang tidak dapat kami lakukan untuk Anda.
Izinkan saya menulis sesuatu untuk hasil edit terakhir Anda, di mana Anda menulis
dan kemudian dapat melakukan iterasi tanpa melakukan transmisi berdasarkan jenis waktu proses.
Mentransmisikan berdasarkan jenis waktu proses belum tentu menjadi masalah. Ini hanya menjadi masalah jika membuat ekstensi ke Event
hierarki kelas lebih sulit, karena kode Klien yang ada akan dipaksa untuk berubah dengan setiap ekstensi.
Misalnya, ada kode klien yang menangani semua peristiwa obrolan dengan tes tipe plus cast for ChatEvent
. Jika jenis acara baru ditambahkan yang bukan acara obrolan, kode yang ada masih akan berfungsi. Jika acara seperti obrolan baru ditambahkan, sebagai turunan dari ChatEvent
, kode yang ada juga akan tetap berfungsi selama ChatEvent
jenisnya sesuai dengan LSP. Untuk acara obrolan tertentu, polimorfisme dapat digunakan di dalam ChatEvent
bagian dari pohon warisan.
Jadi, alih-alih menghindari tes jenis dan pemeran takhayul dalam semua keadaan, karena Anda telah membaca di buku teks "ini umumnya buruk", renungkan mengapa dan kapan hal ini benar-benar menyebabkan masalah . Dan seperti yang saya tulis di atas, menulis beberapa kode klien untuk beberapa kasus penggunaan nyata akan membantu Anda mendapatkan pemahaman yang lebih baik tentang ini. Ini akan memungkinkan Anda juga untuk memvalidasi apa yang akan terjadi ketika daftar acara Anda diperpanjang setelahnya.
Daripada berkonsentrasi pada data, coba pikirkan lebih banyak tentang apa yang seharusnya dilakukannya .
Jadi, an Event
seharusnya merekam sesuatu yang terjadi di dalam game. Saya membayangkan satu-satunya hal yang benar-benar Anda inginkan dari Event
adalah memutarnya kembali (saya tahu Anda memiliki kasus penggunaan lain, dengarkan saya :). Itu berarti sesuatu seperti:
public interface Event {
void replayOn(Game game);
}
Perhatikan, Anda dapat "mempertahankan pemesanan", karena Anda tidak harus tahu persis jenis acara yang Anda coba putar ulang. Anda tidak harus memiliki enum atau "properti" lainnya untuk membedakan berbagai jenis peristiwa. Bagaimanapun, ini akan menjadi anti-pola.
Namun, Anda tetap harus mendefinisikan file Game
. Di sinilah Anda menggambarkan hal-hal yang dapat terjadi dalam definisi Anda tentang permainan catur:
public interface Game {
void move(...);
void toggleClock(...);
void sendText(...);
}
Sekarang, jika Anda ingin menganalisis obrolan, Anda akan membuat implementasi Game
antarmuka yang mengabaikan semua metode selain sendText()
misalnya, dan Anda membiarkan semua peristiwa diputar ulang pada implementasi ini. Jika Anda ingin memutar ulang di UI, Anda membuat implementasi Game
untuk itu. Dan seterusnya.
Perhatikan juga, dalam skenario ini Anda tidak harus mengekspos List<Event>
struktur, hanya file Event
. Sebuah Event
dapat berisi beberapa peristiwa "atom" jika diinginkan, karena ia hanya didefinisikan dalam istilah apa yang dilakukannya, bukan apa yang dikandungnya.
Jadi misalnya ini adalah acara yang valid:
public final class Events implements Event {
private final List<Event> events;
...
@Override
public void replayOn(Game game) {
events.forEach(event -> event.replayOn(game));
}
}
Adapun "pola" apa ini, tidak terlalu penting. Orang dapat berargumen bahwa ini adalah bentuk sumber peristiwa, karena status permainan dibangun dari transisi status. Ini juga hampir melakukan pengiriman ganda / pengunjung, kecuali tidak menggunakan tipe untuk melakukan langkah kedua, tetapi metode yang relevan dengan domain nyata.
Ini tentu saja berorientasi objek, karena tidak ada titik data ditarik keluar dari suatu objek.
Saya setuju dengan jawaban yang diposting bahwa Anda terlalu merekayasa pendekatan Anda. Selain itu, ada beberapa opsi di sini, dan Anda cukup memahami detail dan pertimbangan yang akan membantu memutuskan di antara opsi-opsi tersebut.
Tetapi saya kebetulan telah menangani masalah yang sama belum lama ini, jadi saya ingin memberi Anda contoh dunia nyata tentang bagaimana masalah Anda dapat ditangani.
Backend
Dalam kasus kami, kami mengembalikan serangkaian acara dari semua jenis (pengguna dibuat, pengguna diperbarui, ...) tetapi harus berupa daftar tunggal, tanpa filter khusus (selain penomoran halaman).
Karena ada banyak sekali jenis acara, dan karena pertimbangan, acara itu dijaga seminimal mungkin, kami memilih untuk membuat serial data acara dan menyimpannya dengan cara ini. Ini berarti bahwa penyimpanan data kami tidak perlu diperbarui setiap kali ada acara baru yang dikembangkan.
Contoh cepat. Ini adalah peristiwa yang ditangkap:
public class UserCreated
{
public Guid UserId { get; set; }
}
public class UserDeleted
{
public Guid UserId { get; set; }
}
Perhatikan bahwa acara kami benar-benar diminimalkan. Anda akan mendapatkan lebih banyak data di sini, tetapi prinsipnya tetap sama.
Dan alih-alih menyimpannya langsung dalam tabel, kami menyimpan data serialnya dalam tabel:
public class StoredEvent
{
public Guid Id { get; set; }
public DateTime Timestamp { get; set; }
public string EventType { get; set; }
public string EventData { get; set; }
}
EventType
berisi nama tipe (misalnya MyApp.Domain.Events.UserCreated
), EventData
berisi JSON berseri (misalnya { "id" : "1c8e816f-6126-4ceb-82b1-fa66e237500b" }
).
Ini berarti bahwa kami tidak perlu memperbarui penyimpanan data kami untuk setiap jenis acara yang ditambahkan, alih-alih dapat menggunakan kembali penyimpanan data yang sama untuk semua acara, karena mereka adalah bagian dari satu antrian.
Karena peristiwa ini tidak perlu difilter (yang juga merupakan salah satu persyaratan Anda), ini berarti bahwa API kami tidak pernah perlu melakukan deserialisasi data untuk menafsirkannya. Sebaliknya, API kami hanya mengembalikan StoredEvent
data (yah, DTO, tetapi dengan properti yang sama) ke konsumen.
Ini menyimpulkan bagaimana backend disiapkan, dan langsung menjawab pertanyaan yang Anda ajukan di sini.
Singkatnya, dengan mengembalikan dua properti (yaitu data peristiwa berseri dan jenis peristiwa tertentu), Anda dapat mengembalikan variasi besar jenis peristiwa dalam satu daftar, tanpa perlu memperbarui logika ini setiap kali jenis peristiwa baru akan ditambahkan. Ini adalah bukti masa depan dan ramah OCP.
Bagian selanjutnya berfokus pada contoh khusus tentang cara kami memilih untuk menggunakan umpan ini dalam aplikasi konsumen kami. Ini mungkin atau mungkin tidak sesuai dengan harapan Anda - ini hanya contoh dari apa yang dapat Anda lakukan dengan ini.
Bagaimana Anda mendesain konsumen Anda terserah Anda. Tetapi desain backend yang dibahas di sini akan kompatibel dengan sebagian besar, jika tidak semua cara Anda dapat merancang konsumen Anda.
Paling depan
Dalam kasus kami, konsumen akan menjadi aplikasi C # yang lain, jadi kami mengembangkan pustaka klien yang akan menggunakan API kami, dan akan men-deserialisasi kejadian yang disimpan kembali ke kelas acara masing-masing.
Konsumen akan menginstal paket Nuget kami dibuat tersedia, yang berisi kelas acara ( UserCreated
, UserDeleted
, ...) dan antarmuka ( IHandler<TEventType>
) bahwa konsumen akan digunakan untuk menentukan bagaimana setiap peristiwa harus ditangani.
Secara internal, paket tersebut juga berisi layanan acara. Layanan ini akan melakukan tiga hal:
- Buat kueri REST API untuk mengambil acara
- Ubah acara yang disimpan kembali ke kelas masing-masing
- Kirim setiap acara ini ke pengendali terdaftar mereka
Langkah 1 tidak lebih dari HTTP Dapatkan panggilan ke titik akhir kami.
Langkah 2 ternyata sangat sederhana, jika Anda memiliki tipe dan data:
var originalEvent = JsonConvert.DeserializeObject(storedEvent.EventData, storedEvent.EventType);
Langkah 3 bergantung pada konsumen yang memiliki penangan yang ditentukan untuk setiap jenis yang mereka minati. Misalnya:
public class UserEventHandlers : IHandler<UserCreated>, IHandler<UserDeleted>
{
public void Handle(UserCreated e)
{
Console.WriteLine($"User {e.UserId} was created!"); } public void Handle(UserDeleted e) { Console.WriteLine($"User {e.UserId} was deleted!");
}
}
Jika konsumen tidak tertarik dalam jenis acara tertentu, mereka akan hanya tidak membuat handler untuk jenis dan karenanya setiap peristiwa dari jenis yang akan efektif diabaikan.
Ini juga membuat semuanya kompatibel ke belakang. Jika jenis acara baru ditambahkan besok, tetapi konsumen ini tidak akan tertarik, Anda dapat membuat konsumen ini tidak tersentuh. Itu tidak akan rusak karena jenis peristiwa baru (itu hanya akan mengabaikan jenis baru tersebut), dan tidak akan memaksa Anda untuk menerapkan ulang aplikasi Anda.
Satu-satunya penyebab nyata untuk pemindahan adalah jika perubahan dilakukan pada jenis peristiwa yang benar-benar diminati konsumen, dan itu secara logis tidak dapat dihindari.
Dalam skenario yang ideal, saya akan membayangkan penangan penulisan pengguna akhir ke jenis acara yang mereka pedulikan, dan kemudian dapat mengulang tanpa casting berdasarkan jenis runtime.
Saya bisa berempati dengan sentimen di sini: pasti harus ada beberapa cara lain untuk melakukan ini karena melihat jenis adalah kode bau , kan? Kita mungkin semua telah melihat kode yang melakukan hal-hal berbulu dengan mengambil object
dan melakukan beberapa jenis pemeriksaan yang buruk di atasnya, yang mengarah ke beberapa anti-pola.
Mari kita lihat bagian lain dari pertanyaan Anda:
Tantangannya adalah bahwa pengurutan antara peristiwa harus dipertahankan, jadi saya tidak dapat memisahkan dengan metode seperti
getMoveEvents
dangetTimerEvents
karenaTimerEvent
dapat terjadi antara peristiwa pemindahan dan klien mungkin memerlukan informasi tersebut.
Memperluas logika ini - jika kita melihat penangan yang benar-benar umum, kita mengatakan bahwa:
- Itu bisa peduli menangani jenis tunggal apa pun
- Atau mungkin peduli menangani berbagai jenis
- Jenis yang berbeda mungkin tidak independen satu sama lain
- Itu membutuhkan item dari jenis yang berbeda dalam urutan interleaved
Pada dasarnya, ini bermuara pada mengatakan bahwa kita tidak mengetahui interdependensi dalam logika pemrosesan, hanya saja itu harus diatur waktu. Ini berarti kita tidak dapat menulis penangan tipe tunggal, dan jika kita menulis sesuatu seperti "dapatkan semua item dari tipe A dan B dan C, dan kirim mereka menggunakan penangan A dan B dan C" kita mungkin menemukan penangan A dan B itu perlu bekerja sama untuk melakukan pemrosesan - yang sangat rumit. Apakah ada sesuatu yang lebih sederhana namun tetap fleksibel?
Nah, bagaimana programmer secara historis memecahkan masalah jenis ini? Pertama, saya pikir ada baiknya menunjukkan bahwa ada banyak istilah yang saling terkait yang muncul di komentar dan jawaban di sini yang mengarah ke solusi yang pada dasarnya sama: "tipe data aljabar" dan "tipe penjumlahan", dan saya akan menambahkan sedikit juga - "serikat terdiskriminasi" , "serikat tagged", dan "varian". Mungkin ada beberapa perbedaan di sini, tetapi temanya adalah bahwa semuanya dapat terlihat sangat mirip dengan deskripsi Anda Event
- mereka adalah subtipe yang dapat membawa data, tetapi mereka harus berbeda dari, katakanlah, lebih umum object
. Istilah terkait lainnya yang disebutkan adalah "pencocokan pola", yang terkait dengan cara Anda bekerja dengan serikat yang terdiskriminasi.
Seperti yang bisa Anda tebak dari banyak nama yang digunakan di atas, ini memang masalah yang berulang; ini cenderung menjadi solusi berulang untuk berbagai bahasa. Konstruksi ini biasanya diterapkan pada tingkat bahasa - atau ditiru ketika bahasa tidak mendukungnya. Ini juga bukan hanya sesuatu dari masa lalu yang jauh atau sepenuhnya digantikan oleh konstruksi lain - misalnya, C # 8.0 memperluas pencocokan pola dari C # 7.0 pada 2019 .
Sekarang, saya khawatir jika Anda belum pernah melihatnya sebelumnya - Anda mungkin tidak menyukai seperti apa solusi yang dihormati waktu ini. Inilah contoh kode C # 7.0 yang lebih lama dari tautan di atas:
Fruit fruit = new Apple { Color = Color.Green };
switch (fruit)
{
case Apple apple when apple.Color == Color.Green:
MakeApplePieFrom(apple);
break;
case Apple apple when apple.Color == Color.Brown:
ThrowAway(apple);
break;
case Apple apple:
Eat(apple);
break;
case Orange orange:
orange.Peel();
break;
}
Atau contoh Swift :
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
Dan jika Anda membersihkannya, Anda dapat melakukan sesuatu seperti ini di F #:
let getShapeWidth shape =
match shape with
| Rectangle(width = w) -> w
| Circle(radius = r) -> 2. * r
| Prism(width = w) -> w
Jadi kita kembali ke lingkaran penuh, setidaknya jika kita menyipitkan mata sedikit. Solusi yang berulang secara luas memiliki beberapa kecerdasan dan gula sintaksis, tetapi ... sepertinya versi sakelar sakelar yang lebih aman!
Apakah bahasa yang Anda gunakan memiliki beberapa versi dari konsep ini?
Pertimbangkan untuk menggunakan nomor urut. Namun, saya pikir ada baiknya melihat kebutuhan Anda terlebih dahulu:
Saya akan membayangkan pengguna akhir menulis penangan untuk jenis acara yang mereka pedulikan, dan kemudian dapat mengulang tanpa casting berdasarkan jenis runtime.
Ini secara langsung bertentangan dengan
Saya ingin merancang API yang dapat menyimpan dan memaparkan Daftar pada dasarnya kepada klien yang dapat memilih untuk memproses berbagai peristiwa ini sesuai keinginan.
Anda benar-benar tidak bisa melakukan keduanya. Anda juga mengekspos informasi dalam bentuk yang diketik, atau formulir umum. Tetapi untuk memiliki API yang mengeluarkannya dalam bentuk generictyped (atau typedgeneric) tidaklah mungkin. Anda bisa menghapus informasi atau tidak.
Sebagai solusinya, kami dapat melonggarkan salah satu aturan Anda
Saya tidak dapat memisahkan dengan metode seperti getMoveEvents dan getTimerEvents karena TimerEvent dapat terjadi antara peristiwa pemindahan dan klien mungkin memerlukan informasi tersebut.
Pertimbangkan ini sebagai solusi: Setiap peristiwa dalam sistem diberi "nomor urut" unik yang dimulai dari 1 dan dihitung ke atas (saya suka memulai dari 1 sehingga 0 bisa menjadi "nomor urut yang tidak valid"). Nomor urut itu disimpan di Event
objek.
Sekarang Anda dapat memiliki getMoveEvents()
, yang mengembalikan daftar terurut dari semua MoveEvents
, dan a getTimerEvents()
, yang mengembalikan daftar terurut dari semua TimerEvents
. Algoritme apa pun yang perlu memahami interaksi antara peristiwa dari jenis yang berbeda dapat melihat nomor urut. Jika saya memiliki [Pindah (seqnum = 1), Pindah (seqnum = 3)] dan [Pewaktu (seqnum = 2)], cukup mudah untuk melihat bahwa urutan kejadian adalah Pindah, Pewaktu, Pindah.
Logikanya di sini adalah bahwa pengguna Anda mengetahui jenis data yang ingin mereka operasikan (seperti MoveEvents
). Maka masuk akal bagi mereka untuk mengetahui fungsi khusus tipe untuk dipanggil untuk mendapatkan daftar.
Pengguna kemudian dapat menggabungkan acara dengan cara apa pun yang mereka suka. Sebagai contoh, pertimbangkan algoritma yang melihat MoveEvents
dan TimerEvents
, dan tidak ada yang lain. Itu bisa memiliki API seperti:
enum EventType {
MOVE,
TIMER
};
bool moveNext(); // returns true if there's another event to move to
EventType getCurrentType();
MoveEvent getCurrentMoveEvent(); // error if current type is TIMER
TimerEvent getCurrentTimerEvent(); // error if current type is MOVE
Kemudian hanya perlu mengulangi setiap daftar, menemukan daftar mana yang memiliki nomor urut bernomor lebih rendah, dan itulah acara berikutnya. Perhatikan bahwa saya tidak melakukan pengecoran, dan pencacahannya khusus untuk algoritme - algoritme yang berbeda dapat mempertahankan daftar Peristiwa yang dimusnahkan untuk dipertimbangkan.
Jika Anda melihat lompatan nomor urut (lebih dari 1), maka Anda tahu bahwa ada peristiwa yang terjadi dari jenis yang tidak Anda tangani. Terserah algoritme Anda untuk memutuskan apakah itu adalah kesalahan, atau apakah Anda dapat mengabaikan kejadian yang tidak dikenal. Biasanya cukup jelas yang mana.
Jika Event
kelas Anda memiliki sesuatu selain nomor urut, Anda juga dapat mengekspos List<Event>
semua peristiwa sebagai cara untuk memahaminya. Seseorang selalu dapat menemukan nomor urut dari suatu peristiwa yang diminati, kemudian mencarinya di peristiwa yang diketik yang diketahuinya. Namun, jika Anda tidak memaparkan informasi tambahan, ini tidak perlu List<Event>
. Kita tahu urutan nomor urut acara di: 1, 2, 3, 4 ...
Contoh algoritme yang dapat menggunakan pola ini: Tetapkan setiap gerakan dalam rentang waktu tertentu saat gerakan tersebut dapat terjadi. Jika Anda hanya memindai daftar MoveEvent
dan TimerEvent
, Anda dapat menemukan dua TimerEvents
yang masing-masing memiliki batas nomor urut MoveEvent
. Karena Anda tahu peristiwa terjadi dalam urutan nomor urut, Anda tahu bahwa perpindahan pasti terjadi antara stempel waktu pada yang pertama TimerEvent
dan kedua.
Meskipun contoh kode sumber Anda sangat terinspirasi oleh Java, yang Anda minta adalah tipe penjumlahan , yang merupakan tipe yang dibentuk dari gabungan tipe lainnya.
Dalam contoh Anda di atas, dalam bahasa seperti karat:
struct Move {
previous: (u8,u8),
new: (u8,u8)
}
struct GameTimer {
timestamp: i64,
new_player_id: i64,
}
struct Message {
timestamp: i64,
new_player_id: i64,
message: String
}
enum Event {
Move(Move),
Timer(GameTimer),
ChatMessage(Message)
}
fn length_all_chats(events: Vec<Event>) -> usize {
events.iter().fold(0, |sum, event|
sum + match event {
Event::Move(_) => 0,
Event::Timer(_) => 0,
Event::ChatMessage(Message{message: msg, ..}) => msg.len(),
}
)
}
Di atas length_all_chats
mengembalikan jumlah panjang semua pesan obrolan dalam daftar acara.
Jika jenis acara baru diperkenalkan, konsumen perlu memperbarui untuk mengkompilasi (atau menyediakan pola penampung-semua). Ini adalah cara berbeda dalam mengimplementasikan polimorfisme waktu proses, yang memungkinkan pola yang lebih kuat, seperti beberapa pengiriman tempat Anda dapat memanggil fungsi yang berbeda berdasarkan jenis dari dua (atau lebih) argumen.