Dalam contoh kode berikut, kami memiliki kelas untuk objek yang tidak dapat diubah yang mewakili ruangan. Utara, Selatan, Timur, dan Barat mewakili pintu keluar ke kamar lain.
public sealed class Room
{
public Room(string name, Room northExit, Room southExit, Room eastExit, Room westExit)
{
this.Name = name;
this.North = northExit;
this.South = southExit;
this.East = eastExit;
this.West = westExit;
}
public string Name { get; }
public Room North { get; }
public Room South { get; }
public Room East { get; }
public Room West { get; }
}
Jadi kita lihat, kelas ini dirancang dengan referensi lingkaran refleksif. Tetapi karena kelasnya tidak berubah, saya terjebak dengan masalah 'ayam atau telur'. Saya yakin bahwa programmer fungsional yang berpengalaman tahu bagaimana menghadapi ini. Bagaimana bisa ditangani dalam C #?
Saya berusaha keras untuk membuat kode game petualangan berbasis teks, tetapi menggunakan prinsip pemrograman fungsional hanya untuk tujuan pembelajaran. Saya terjebak pada konsep ini dan dapat menggunakan bantuan !!! Terima kasih.
MEMPERBARUI:
Berikut ini adalah implementasi yang bekerja berdasarkan jawaban Mike Nakis mengenai inisialisasi malas:
using System;
public sealed class Room
{
private readonly Func<Room> north;
private readonly Func<Room> south;
private readonly Func<Room> east;
private readonly Func<Room> west;
public Room(
string name,
Func<Room> northExit = null,
Func<Room> southExit = null,
Func<Room> eastExit = null,
Func<Room> westExit = null)
{
this.Name = name;
var dummyDelegate = new Func<Room>(() => { return null; });
this.north = northExit ?? dummyDelegate;
this.south = southExit ?? dummyDelegate;
this.east = eastExit ?? dummyDelegate;
this.west = westExit ?? dummyDelegate;
}
public string Name { get; }
public override string ToString()
{
return this.Name;
}
public Room North
{
get { return this.north(); }
}
public Room South
{
get { return this.south(); }
}
public Room East
{
get { return this.east(); }
}
public Room West
{
get { return this.west(); }
}
public static void Main(string[] args)
{
Room kitchen = null;
Room library = null;
kitchen = new Room(
name: "Kitchen",
northExit: () => library
);
library = new Room(
name: "Library",
southExit: () => kitchen
);
Console.WriteLine(
$"The {kitchen} has a northen exit that " +
$"leads to the {kitchen.North}.");
Console.WriteLine(
$"The {library} has a southern exit that " +
$"leads to the {library.South}.");
Console.ReadKey();
}
}
sumber
Room
contoh Anda .type List a = Nil | Cons of a * List a
. Dan pohon biner:type Tree a = Leaf a | Cons of Tree a * Tree a
. Seperti yang Anda lihat, mereka berdua merujuk pada diri sendiri (rekursif). Berikut adalah bagaimana Anda akan mendefinisikan kamar Anda:type Room = Nil | Open of {name: string, south: Room, east: Room, north: Room, west: Room}
.Room
kelas Anda dan aList
dalam Haskell yang saya tulis di atas.Jawaban:
Jelas, Anda tidak dapat melakukannya dengan menggunakan kode yang Anda poskan, karena pada titik tertentu Anda perlu membuat objek yang perlu dihubungkan ke objek lain yang belum dibangun.
Ada dua cara yang dapat saya pikirkan (yang telah saya gunakan sebelumnya) untuk melakukan ini:
Menggunakan dua fase
Semua objek dibangun terlebih dahulu, tanpa ketergantungan apa pun, dan begitu semuanya dibangun, mereka terhubung. Ini berarti bahwa objek-objek harus melalui dua fase dalam hidup mereka: fase yang sangat bisa berubah, diikuti oleh fase yang tidak dapat berubah yang berlangsung sepanjang sisa hidup mereka.
Anda bisa menemukan masalah yang sama persis ketika memodelkan basis data relasional: satu tabel memiliki kunci asing yang menunjuk ke tabel lain, dan tabel lainnya mungkin memiliki kunci asing yang menunjuk ke tabel pertama. Cara ini ditangani dalam database relasional adalah bahwa batasan kunci asing dapat (dan biasanya) ditentukan dengan
ALTER TABLE ADD FOREIGN KEY
pernyataan tambahan yang terpisah dariCREATE TABLE
pernyataan itu. Jadi, pertama Anda membuat semua tabel Anda, lalu Anda menambahkan batasan kunci asing Anda.Perbedaan antara basis data relasional dan apa yang ingin Anda lakukan adalah bahwa basis data relasional terus memperbolehkan
ALTER TABLE ADD/DROP FOREIGN KEY
pernyataan sepanjang umur tabel, sementara Anda mungkin akan menetapkan flag 'IamImmutable` dan menolak mutasi lebih lanjut setelah semua dependensi telah direalisasikan.Menggunakan inisialisasi malas
Alih-alih referensi ke dependensi Anda melewati delegasi yang akan mengembalikan referensi ke dependensi bila diperlukan. Setelah ketergantungan diambil, delegasi tidak pernah dipanggil lagi.
Delegasi biasanya akan mengambil bentuk ekspresi lambda, sehingga hanya akan terlihat sedikit lebih bertele-tele daripada benar-benar memberikan dependensi kepada konstruktor.
Kelemahan (kecil) dari teknik ini adalah Anda harus membuang ruang penyimpanan yang diperlukan untuk menyimpan pointer ke delegasi yang hanya akan digunakan selama inisialisasi grafik objek Anda.
Anda bahkan dapat membuat kelas "referensi malas" generik yang mengimplementasikan ini sehingga Anda tidak perlu mengimplementasikannya kembali untuk setiap anggota.
Ini adalah kelas yang ditulis dalam Java, Anda dapat dengan mudah menuliskannya dalam C #
(My
Function<T>
sepertiFunc<T>
delegasi C #)Kelas ini seharusnya aman untuk thread, dan hal-hal "periksa ganda" terkait dengan optimasi dalam kasus konkurensi. Jika Anda tidak berencana melakukan multi-utas, Anda dapat menghapus semua itu. Jika Anda memutuskan untuk menggunakan kelas ini dalam pengaturan multi-utas, pastikan untuk membaca tentang "idiom periksa ganda". (Ini adalah diskusi panjang di luar ruang lingkup pertanyaan ini.)
sumber
Pola inisialisasi malas dalam jawaban Mike Nakis bekerja dengan baik untuk inisialisasi satu kali antara dua objek, tetapi menjadi sulit untuk beberapa objek yang saling terkait dengan pembaruan yang sering.
Jauh lebih sederhana dan lebih mudah dikelola untuk menjaga hubungan antar kamar di luar objek itu sendiri, dalam sesuatu seperti sebuah
ImmutableDictionary<Tuple<int, int>, Room>
. Dengan begitu, alih-alih membuat referensi melingkar, Anda hanya menambahkan referensi satu arah yang mudah diperbarui ke kamus ini.sumber
Room
dari muncul untuk memiliki hubungan mereka; tetapi, mereka harus getter yang hanya membaca dari indeks.Cara untuk melakukan ini dalam gaya fungsional adalah mengenali apa yang sebenarnya Anda buat: grafik berarah dengan tepi berlabel.
Penjara bawah tanah adalah struktur data yang melacak sekelompok kamar dan hal-hal, dan apa hubungan di antara mereka. Setiap panggilan "dengan" mengembalikan ruang bawah tanah yang baru dan berbeda . Kamar-kamar tidak tahu apa yang utara dan selatan dari mereka; buku itu tidak tahu itu ada di peti. The dungeon tahu fakta-fakta, dan bahwa hal tidak memiliki masalah dengan referensi melingkar karena tidak ada satu pun.
sumber
Ayam dan telur benar. Ini tidak masuk akal di c #:
Tapi ini memang:
Tetapi itu berarti A tidak abadi!
Anda bisa menipu:
Itu menyembunyikan masalahnya. Tentu A dan B memiliki keadaan tidak berubah tetapi mereka merujuk pada sesuatu yang tidak abadi. Yang bisa dengan mudah mengalahkan titik membuat mereka tidak berubah. Saya harap C setidaknya seaman thread yang Anda butuhkan.
Ada pola yang disebut freeze-thaw:
Sekarang 'a' tidak berubah. 'A' bukan tapi 'a'. Kenapa tidak apa-apa? Selama tidak ada lagi yang tahu tentang 'a' sebelum beku, siapa yang peduli?
Ada metode pencairan () tetapi tidak pernah mengubah 'a'. Itu membuat salinan bisa berubah dari 'a' yang dapat diperbarui kemudian dibekukan juga.
Kelemahan dari pendekatan ini adalah bahwa kelas tidak memaksakan imutabilitas. Prosedur berikut adalah. Anda tidak dapat mengetahui apakah itu tidak stabil dari tipe.
Saya tidak benar-benar tahu cara ideal untuk menyelesaikan masalah ini di c #. Saya tahu cara untuk menyembunyikan masalah. Kadang itu sudah cukup.
Ketika tidak, saya menggunakan pendekatan yang berbeda untuk menghindari masalah ini sama sekali. Sebagai contoh: lihat bagaimana pola negara diterapkan di sini . Anda akan berpikir mereka akan melakukan itu sebagai referensi melingkar tetapi mereka tidak melakukannya. Mereka membuat objek baru setiap kali keadaan berubah. Terkadang lebih mudah menyalahgunakan pemulung untuk mencari tahu cara mengeluarkan telur dari ayam.
sumber
a.freeze()
dapat mengembalikanImmutableA
tipe. yang membuatnya pada dasarnya pola pembangun.b
dibiarkan memegang referensi ke yang bisa berubah lamaa
. Idenya adalah itua
danb
harus menunjuk ke versi yang tidak berubah dari satu sama lain sebelum Anda melepaskannya ke seluruh sistem.Beberapa orang pintar sudah menyuarakan pendapat mereka tentang hal ini, tetapi saya hanya berpikir bahwa itu bukan tanggung jawab ruangan untuk mengetahui apa tetangga mereka.
Saya pikir itu tanggung jawab gedung untuk mengetahui di mana kamar berada. Jika ruangan benar-benar perlu tahu tetangganya melewati INeigbourFinder untuk itu.
sumber