Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Delegasi menyediakan mekanisme yang memungkinkan desain perangkat lunak yang melibatkan kopling minimal antar komponen.
Salah satu contoh yang sangat baik untuk desain semacam ini adalah LINQ. Pola Ekspresi Kueri LINQ mengandalkan delegat untuk semua fiturnya. Pertimbangkan contoh sederhana ini:
var smallNumbers = numbers.Where(n => n < 10);
Ini memfilter urutan angka hanya untuk yang kurang dari nilai 10.
Metode ini Where menggunakan delegasi yang menentukan elemen urutan mana yang melewati filter. Saat Anda membuat kueri LINQ, Anda menyediakan implementasi delegasi untuk tujuan khusus ini.
Prototipe untuk metode Where adalah:
public static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource, bool> predicate);
Contoh ini diulang dengan semua metode yang merupakan bagian dari LINQ. Mereka semua mengandalkan delegasi untuk kode yang mengelola permintaan data tertentu. Pola desain API ini adalah pola yang kuat untuk dipelajari dan dipahami.
Contoh sederhana ini menggambarkan bagaimana delegasi membutuhkan sedikit kopling antar komponen. Anda tidak perlu membuat kelas yang berasal dari kelas dasar tertentu. Anda tidak perlu menerapkan antarmuka tertentu. Satu-satunya persyaratan adalah memberikan implementasi satu metode yang mendasar untuk tugas yang ditangani.
Bangun Komponen Anda Sendiri dengan Delegasi
Mari kita kembangkan contoh tersebut dengan membuat komponen menggunakan desain yang mengandalkan delegasi.
Mari kita tentukan komponen yang dapat digunakan untuk pesan log dalam sistem besar. Komponen pustaka dapat digunakan di banyak lingkungan yang berbeda, pada beberapa platform yang berbeda. Ada banyak fitur umum dalam komponen yang mengelola log. Ini perlu menerima pesan dari komponen apa pun dalam sistem. Pesan tersebut akan memiliki prioritas yang berbeda, yang dapat dikelola komponen inti. Pesan harus memiliki tanda waktu dalam formulir arsip terakhirnya. Untuk skenario yang lebih canggih, Anda dapat memfilter pesan menurut komponen sumber.
Ada satu aspek fitur yang akan sering berubah: di mana pesan ditulis. Di beberapa lingkungan, mereka mungkin ditulis ke konsol kesalahan. "Dalam kasus lain, sebuah file." Kemungkinan lain termasuk penyimpanan database, log peristiwa OS, atau penyimpanan dokumen lainnya.
Ada juga kombinasi output yang mungkin digunakan dalam skenario yang berbeda. Anda mungkin ingin menulis pesan ke konsol dan ke file.
Desain berdasarkan delegasi akan memberikan banyak fleksibilitas, dan memudahkan untuk mendukung mekanisme penyimpanan yang mungkin ditambahkan di masa depan.
Di bawah desain ini, komponen log utama dapat menjadi kelas non-virtual, bahkan disegel. Anda dapat menghubungkan sekelompok wakil untuk mencatat pesan ke media penyimpanan yang berbeda. Dukungan bawaan untuk delegasi multicast memudahkan untuk mendukung skenario di mana pesan harus ditulis ke beberapa lokasi (file, dan konsol).
Implementasi pertama
Mari kita mulai dari yang kecil: implementasi awal akan menerima pesan baru, dan menulis pesan-pesan tersebut menggunakan delegasi yang terlampir. Anda dapat memulai dengan satu delegasi yang menulis pesan ke konsol.
public static class Logger
{
public static Action<string>? WriteMessage;
public static void LogMessage(string msg)
{
if (WriteMessage is not null)
WriteMessage(msg);
}
}
Kelas statis di atas adalah hal paling sederhana yang dapat bekerja. Kita perlu menulis implementasi tunggal untuk metode yang menulis pesan ke konsol:
public static class LoggingMethods
{
public static void LogToConsole(string message)
{
Console.Error.WriteLine(message);
}
}
Terakhir, Anda perlu menghubungkan delegat dengan mengaitkannya ke delegat WriteMessage yang dideklarasikan dalam log.
Logger.WriteMessage += LoggingMethods.LogToConsole;
Praktek
Sampel kami sejauh ini cukup sederhana, tetapi masih menunjukkan beberapa pedoman penting untuk desain yang melibatkan delegasi.
Menggunakan jenis delegasi yang ditentukan dalam kerangka kerja inti memudahkan pengguna untuk bekerja dengan delegasi. Anda tidak perlu menentukan tipe baru, dan pengembang yang menggunakan pustaka Anda tidak perlu mempelajari jenis delegasi khusus yang baru.
Antarmuka yang digunakan minimal dan fleksibel mungkin: Untuk membuat pencatat output baru, Anda harus membuat satu metode. Metode itu mungkin metode statis, atau metode instans. Ini mungkin dapat mengakses apa saja.
Format Keluaran
Mari kita buat versi pertama ini sedikit lebih kuat, lalu mulai membuat mekanisme pengelogan lainnya.
Selanjutnya, mari kita tambahkan beberapa argumen ke LogMessage() metode sehingga kelas log Anda membuat pesan yang lebih terstruktur:
public enum Severity
{
Verbose,
Trace,
Information,
Warning,
Error,
Critical
}
public static class Logger
{
public static Action<string>? WriteMessage;
public static void LogMessage(Severity s, string component, string msg)
{
var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
if (WriteMessage is not null)
WriteMessage(outputMsg);
}
}
Selanjutnya, mari kita gunakan argumen tersebut Severity untuk memfilter pesan yang dikirim ke output log.
public static class Logger
{
public static Action<string>? WriteMessage;
public static Severity LogLevel { get; set; } = Severity.Warning;
public static void LogMessage(Severity s, string component, string msg)
{
if (s < LogLevel)
return;
var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
if (WriteMessage is not null)
WriteMessage(outputMsg);
}
}
Praktek
Anda telah menambahkan fitur baru ke infrastruktur pengelogan. Karena komponen pencatat sangat longgar digabungkan dengan mekanisme output apa pun, fitur baru ini dapat ditambahkan tanpa dampak pada kode apa pun yang menerapkan delegasi pencatat.
Saat Anda terus membangun ini, Anda akan melihat lebih banyak contoh tentang bagaimana kopling longgar ini memungkinkan fleksibilitas yang lebih besar dalam memperbarui bagian situs tanpa perubahan pada lokasi lain. Bahkan, dalam aplikasi yang lebih besar, kelas output pencatat mungkin berada di rakitan yang berbeda, dan bahkan tidak perlu dibangun kembali.
Membangun Mesin Output Kedua
Komponen Log berkembang dengan baik. Mari kita tambahkan satu mesin output lagi yang mencatat pesan ke file. Ini akan menjadi mesin output yang sedikit lebih terlibat. Ini akan menjadi kelas yang merangkum operasi file, dan memastikan bahwa file selalu ditutup setelah setiap penulisan. Itu memastikan bahwa semua data dibersihkan ke disk setelah setiap pesan dihasilkan.
Berikut adalah pencatat berbasis file:
public class FileLogger
{
private readonly string logPath;
public FileLogger(string path)
{
logPath = path;
Logger.WriteMessage += LogMessage;
}
public void DetachLog() => Logger.WriteMessage -= LogMessage;
// make sure this can't throw.
private void LogMessage(string msg)
{
try
{
using (var log = File.AppendText(logPath))
{
log.WriteLine(msg);
log.Flush();
}
}
catch (Exception)
{
// Hmm. We caught an exception while
// logging. We can't really log the
// problem (since it's the log that's failing).
// So, while normally, catching an exception
// and doing nothing isn't wise, it's really the
// only reasonable option here.
}
}
}
Setelah membuat kelas ini, Anda dapat membuat instans dan melampirkan metode LogMessage-nya ke komponen Logger:
var file = new FileLogger("log.txt");
Keduanya tidak saling eksklusif. Anda dapat mengaitkan kedua metode log dan menghasilkan pesan dalam konsol maupun file.
var fileOutput = new FileLogger("log.txt");
Logger.WriteMessage += LoggingMethods.LogToConsole; // LoggingMethods is the static class we utilized earlier
Kemudian, bahkan dalam aplikasi yang sama, Anda dapat menghapus salah satu delegasi tanpa menyebabkan masalah lain pada sistem.
Logger.WriteMessage -= LoggingMethods.LogToConsole;
Praktek
Sekarang, Anda telah menambahkan handler output kedua untuk subsistem pengelogan. Yang satu ini membutuhkan sedikit lebih banyak infrastruktur untuk mendukung sistem file dengan benar. Delegasi adalah metode contoh. Ini juga merupakan metode privat. Tidak perlu aksesibilitas yang lebih besar karena infrastruktur delegasi dapat menghubungkan delegasi.
Kedua, desain berbasis delegasi memungkinkan beberapa metode output tanpa kode tambahan. Anda tidak perlu membangun infrastruktur tambahan apa pun untuk mendukung beberapa metode output. Mereka hanya menjadi metode lain pada daftar pemanggilan.
Beri perhatian khusus pada kode dalam metode output pengelogan file. Ini dikodekan untuk memastikan bahwa itu tidak melemparkan pengecualian apa pun. Meskipun ini tidak selalu diperlukan, ini sering kali merupakan praktik yang baik. Jika salah satu metode delegasi melempar pengecualian, delegasi yang tersisa yang ada di pemanggilan tidak akan dipanggil.
Sebagai catatan terakhir, pencatat file harus mengelola sumber dayanya dengan membuka dan menutup file pada setiap pesan log. Anda dapat memilih untuk menjaga file tetap terbuka dan menerapkan IDisposable untuk menutup file ketika Anda selesai.
Salah satu metode memiliki kelebihan dan kekurangannya. Keduanya sedikit meningkatkan keterhubungan antara kelas.
Tidak ada kode di kelas yang Logger perlu diperbarui untuk mendukung salah satu skenario.
Menangani Delegasi Null
Terakhir, mari kita perbarui metode LogMessage sehingga kuat untuk kasus-kasus tersebut ketika tidak ada mekanisme output yang dipilih. Implementasi saat ini akan melempar NullReferenceException ketika WriteMessage delegasi tidak memiliki daftar pemanggilan yang terlampir.
Anda mungkin lebih suka desain yang diam-diam berlanjut ketika tidak ada metode yang terpasang. Menggunakan operator kondisi null, dikombinasikan dengan metode Delegate.Invoke(), menjadi lebih mudah.
public static void LogMessage(string msg)
{
WriteMessage?.Invoke(msg);
}
Operator bersyarat null (?.) melakukan pendekatan cepat ketika operand kiri (WriteMessage dalam hal ini) adalah null, yang berarti tidak ada upaya untuk mencatat pesan.
Anda tidak akan menemukan metode Invoke() yang tercantum dalam dokumentasi untuk System.Delegate atau System.MulticastDelegate. Pengkompilasi menghasilkan metode aman tipe Invoke untuk setiap jenis delegasi yang dideklarasikan. Dalam contoh ini, itu berarti Invoke mengambil satu string argumen, dan memiliki jenis pengembalian tanpa nilai.
Ringkasan Praktik
Anda telah melihat awalnya komponen pencatatan yang dapat dikembangkan lebih lanjut oleh penulis lain, dan fitur lainnya. Dengan menggunakan delegasi dalam desain, komponen yang berbeda ini terkait secara longgar. Ini memberikan beberapa keuntungan. Sangat mudah untuk membuat mekanisme output baru dan melampirkannya ke sistem. Mekanisme lain ini hanya memerlukan satu metode: metode yang menulis pesan log. Ini adalah desain yang tangguh ketika fitur baru ditambahkan. Kontrak yang diperlukan untuk setiap penulis adalah untuk menerapkan satu metode. Metode itu bisa menjadi metode statis atau instans. Ini bisa berupa akses publik, privat, atau akses hukum lainnya.
Kelas Logger dapat membuat sejumlah peningkatan atau perubahan tanpa memperkenalkan perubahan yang merusak kompatibilitas. Seperti kelas apa pun, Anda tidak dapat memodifikasi API publik tanpa risiko melanggar perubahan. Tetapi, karena penghubung antara perekam log dan mesin keluaran apa pun hanya melalui delegasi, tidak ada jenis lain (seperti antarmuka atau kelas dasar) yang terlibat. Kopling dibuat sekecil mungkin.