Bagikan melalui


Tutorial: Memperbarui antarmuka dengan metode antarmuka default

Anda dapat menentukan implementasi saat mendeklarasikan anggota antarmuka. Skenario yang paling umum adalah menambahkan anggota dengan aman ke antarmuka yang sudah dirilis dan digunakan oleh klien yang tidak terhitung jumlahnya.

Dalam tutorial ini, Anda akan mempelajari cara:

  • Perluas antarmuka dengan aman dengan menambahkan metode dengan implementasi.
  • Buat implementasi berparameter untuk memberikan fleksibilitas yang lebih besar.
  • Memungkinkan pelaksana untuk memberikan implementasi yang lebih spesifik dalam bentuk penggantian.

Prasyarat

Anda perlu menyiapkan komputer Anda untuk menjalankan .NET, termasuk pengkompilasi C#. Pengkompilasi C# tersedia dengan Visual Studio 2022 atau .NET SDK.

Gambaran umum skenario

Tutorial ini dimulai dengan pustaka hubungan pelanggan versi 1. Anda bisa mendapatkan aplikasi pemula pada repositori sampel kami di GitHub. Perusahaan yang membangun perpustakaan ini bermaksud agar pelanggan dengan aplikasi yang ada mengadopsi perpustakaan mereka. Mereka memberikan definisi antarmuka minimal bagi pengguna pustaka mereka untuk diimplementasikan. Berikut adalah definisi antarmuka untuk pelanggan:

public interface ICustomer
{
    IEnumerable<IOrder> PreviousOrders { get; }

    DateTime DateJoined { get; }
    DateTime? LastOrder { get; }
    string Name { get; }
    IDictionary<DateTime, string> Reminders { get; }
}

Mereka mendefinisikan antarmuka kedua yang mewakili urutan:

public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }
}

Dari antarmuka tersebut, tim dapat membangun pustaka bagi pengguna mereka untuk menciptakan pengalaman yang lebih baik bagi pelanggan mereka. Tujuan mereka adalah untuk menciptakan hubungan yang lebih dalam dengan pelanggan yang ada dan meningkatkan hubungan mereka dengan pelanggan baru.

Sekarang, saatnya untuk memperbarui perpustakaan untuk rilis berikutnya. Salah satu fitur yang diminta memungkinkan diskon loyalitas untuk pelanggan yang memiliki banyak pesanan. Diskon loyalitas baru ini diterapkan setiap kali pelanggan membuat pesanan. Diskon khusus adalah properti dari setiap pelanggan individu. Setiap implementasi ICustomer dapat menetapkan aturan yang berbeda untuk diskon loyalitas.

Cara paling alami untuk menambahkan fungsionalitas ini adalah dengan meningkatkan ICustomer antarmuka dengan metode untuk menerapkan diskon loyalitas apa pun. Saran desain ini menyebabkan kekhawatiran di antara pengembang berpengalaman: "Antarmuka tidak dapat diubah setelah dirilis! Jangan membuat perubahan yang melanggar!" Anda harus menggunakan implementasi antarmuka default untuk meningkatkan antarmuka. Penulis pustaka dapat menambahkan anggota baru ke antarmuka dan memberikan implementasi default untuk anggota tersebut.

Implementasi antarmuka default memungkinkan pengembang untuk meningkatkan antarmuka sambil tetap memungkinkan implementor apa pun untuk mengambil alih implementasi tersebut. Pengguna pustaka dapat menerima implementasi default sebagai perubahan yang tidak menyebabkan gangguan. Jika aturan bisnis mereka berbeda, mereka dapat menggantikan.

Peningkatan dengan metode antarmuka bawaan

Tim menyetujui implementasi default yang paling mungkin: diskon loyalitas untuk pelanggan.

Peningkatan harus menyediakan fungsionalitas untuk mengatur dua properti: jumlah pesanan yang diperlukan untuk memenuhi syarat untuk diskon, dan persentase diskon. Fitur-fitur ini menjadikannya skenario yang sempurna untuk metode antarmuka default. Anda dapat menambahkan metode ke ICustomer antarmuka, dan memberikan implementasi yang paling mungkin. Semua yang ada, dan implementasi baru apa pun dapat menggunakan implementasi default, atau menyediakannya sendiri.

Pertama, tambahkan metode baru ke antarmuka, termasuk isi metode :

// This method belongs in the ICustomer interface (ICustomer.cs).
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
    DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
    if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
    {
        return 0.10m;
    }
    return 0;
}

Nota

Contoh sebelumnya menggunakan untuk kesederhanaan DateTime.Now.AddYears(-2) dalam tutorial ini. Ketahuilah bahwa DateTime perhitungan dapat memiliki kasus tepi dengan transisi waktu musim panas dan tahun kabisat. Untuk kode produksi, pertimbangkan untuk menggunakan waktu UTC atau pendekatan perhitungan tanggal yang lebih kuat ketika presisi penting.

Penulis pustaka menulis pengujian pertama untuk memeriksa implementasi:

SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
    Reminders =
    {
        { new DateTime(2010, 08, 12), "childs's birthday" },
        { new DateTime(2012, 11, 15), "anniversary" }
    }
};

SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);
c.AddOrder(o);

o = new SampleOrder(new DateTime(2013, 7, 4), 25m);
c.AddOrder(o);

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Perhatikan bagian tes berikut:

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Konversi implisit dari SampleCustomer ke ICustomer diperlukan. Kelas SampleCustomer tidak perlu menyediakan implementasi untuk ComputeLoyaltyDiscount; yang disediakan oleh ICustomer antarmuka. Namun, kelas SampleCustomer tidak mewarisi anggota dari antarmukanya. Aturan itu belum berubah. Untuk memanggil metode apa pun yang dideklarasikan dan diimplementasikan dalam antarmuka, variabel harus merupakan jenis antarmuka, ICustomer dalam contoh ini.

Berikan parameterisasi

Implementasi default terlalu ketat. Banyak konsumen sistem ini dapat memilih ambang batas yang berbeda untuk jumlah pembelian, panjang keanggotaan yang berbeda, atau diskon persentase yang berbeda. Anda dapat memberikan pengalaman peningkatan yang lebih baik untuk lebih banyak pelanggan dengan menyediakan cara untuk mengatur parameter tersebut. Mari kita tambahkan metode statis yang mengatur ketiga parameter yang mengontrol implementasi default:

// These methods belong in the ICustomer interface (ICustomer.cs).
// Version 2:
public static void SetLoyaltyThresholds(
    TimeSpan ago,
    int minimumOrders = 10,
    decimal percentageDiscount = 0.10m)
{
    length = ago;
    orderCount = minimumOrders;
    discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;

public decimal ComputeLoyaltyDiscount()
{
    DateTime start = DateTime.Now - length;

    if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))
    {
        return discountPercent;
    }
    return 0;
}

Ada banyak kemampuan bahasa baru yang ditunjukkan dalam fragmen kode kecil tersebut. Antarmuka sekarang dapat mencakup anggota statis, termasuk bidang dan metode. Pengubah akses yang berbeda juga diaktifkan. Bidang lainnya bersifat privat, metode baru bersifat publik. Salah satu pengubah diperbolehkan pada anggota antarmuka.

Aplikasi yang menggunakan rumus umum untuk menghitung diskon loyalitas, tetapi parameter yang berbeda, tidak perlu memberikan implementasi kustom; mereka dapat mengatur argumen melalui metode statis. Misalnya, kode berikut menetapkan "apresiasi pelanggan" yang memberi hadiah kepada pelanggan mana pun dengan keanggotaan lebih dari satu bulan:

ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Mengembangkan implementasi default

Kode yang Anda tambahkan sejauh ini telah memberikan implementasi yang nyaman untuk skenario tersebut di mana pengguna menginginkan sesuatu seperti implementasi default, atau untuk memberikan sekumpulan aturan yang tidak terkait. Untuk fitur terakhir, mari kita refaktorisasi kode sedikit untuk mengaktifkan skenario di mana pengguna mungkin ingin mengembangkan berdasarkan implementasi default.

Pertimbangkan startup yang ingin menarik pelanggan baru. Mereka menawarkan diskon 50% dari pesanan pertama pelanggan baru. Jika tidak, pelanggan yang ada mendapatkan diskon standar. Penulis pustaka perlu memindahkan implementasi default ke dalam protected static metode sehingga kelas apa pun yang mengimplementasikan antarmuka ini dapat menggunakan kembali kode dalam implementasinya. Implementasi standar dari anggota antarmuka juga memanggil metode berbagi ini.

// These methods belong in the ICustomer interface (ICustomer.cs).
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);
protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
    DateTime start = DateTime.Now - length;

    if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))
    {
        return discountPercent;
    }
    return 0;
}

Dalam implementasi kelas yang menggunakan antarmuka ini, metode override dapat memanggil metode statis pembantu, dan memperluas logika tersebut untuk memberikan diskon untuk "pelanggan baru".

// This method belongs in the SampleCustomer class (SampleCustomer.cs) that implements ICustomer.
public decimal ComputeLoyaltyDiscount()
{
   if (PreviousOrders.Any() == false)
        return 0.50m;
    else
        return ICustomer.DefaultLoyaltyDiscount(this);
}

Anda dapat melihat seluruh kode yang sudah selesai di repositori sampel kami di GitHub. Anda bisa mendapatkan aplikasi pemula pada repositori sampel kami di GitHub.

Fitur baru ini berarti bahwa antarmuka dapat diperbarui dengan aman ketika ada implementasi default yang wajar untuk anggota baru tersebut. Desain antarmuka dengan hati-hati untuk mengekspresikan ide fungsional tunggal yang diimplementasikan oleh beberapa kelas. Itu membuatnya lebih mudah untuk meningkatkan definisi antarmuka tersebut ketika persyaratan baru ditemukan untuk ide fungsi yang sama.