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 secara aman dengan menambahkan metode dengan implementasi.
  • Buat implementasi parameter untuk memberikan fleksibilitas yang lebih besar.
  • Memungkinkan pelaksana untuk memberikan implementasi yang lebih spesifik dalam bentuk pengambilalihan.

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 versi 1 dari pustaka hubungan pelanggan. Anda bisa mendapatkan aplikasi starter pada repositori sampel kami di GitHub. Perusahaan yang membangun pustaka ini menunjuk pelanggan dengan aplikasi yang ada untuk mengadopsi pustaka mereka. Mereka memberikan definisi antarmuka minimal bagi pengguna pustaka mereka untuk diimplementasikan. Berikut 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 suatu 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 relasi yang lebih mendalam dengan pelanggan yang sudah ada dan meningkatkan hubungan mereka dengan pelanggan baru.

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

Cara paling alami untuk menambahkan fungsi ini adalah dengan meningkatkan ICustomer antarmuka dengan metode untuk menerapkan diskon loyalitas. Saran desain ini menyebabkan kekhawatiran di antara para 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 menyediakan implementasi default untuk anggota tersebut.

Implementasi antarmuka default memungkinkan pengembang untuk meningkatkan antarmuka sambil tetap memungkinkan pelaksana untuk mengambil alih implementasi tersebut. Pengguna pustaka dapat menerima implementasi default sebagai perubahan yang tidak melanggar. Jika aturan bisnis mereka berbeda, mereka dapat mengambil alih.

Meningkatkan dengan metode antarmuka default

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, serta 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 memungkinkan. Semua implementasi baru yang sudah ada dapat menggunakan implementasi default atau menyediakan implementasinya sendiri.

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

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

Penulis pustaka menulis tes 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(1012, 11, 15), "anniversary" }
    }
};

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

o = new SampleOrder(new DateTime(2103, 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()}");

Yang dilemparkan dari SampleCustomer ke ICustomer diperlukan. Kelas SampleCustomer tidak perlu menyediakan implementasi untuk ComputeLoyaltyDiscount; yang disediakan oleh ICustomer antarmuka. Namun, SampleCustomer kelas tidak mewarisi anggota dari antarmukanya. Aturan itu tidak berubah. Untuk memanggil metode yang dideklarasikan dan diimplementasikan dalam antarmuka, variabel harus menjadi jenis antarmuka, dalam contoh ini adalah ICustomer.

Memberikan parameterisasi

Implementasi default terlalu ketat. Banyak konsumen sistem ini dapat memilih ambang yang berbeda untuk jumlah pembelian, lama 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:

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

Terdapat banyak kemampuan bahasa baru yang ditunjukkan pada fragmen kode kecil tersebut. Sekarang, antarmuka 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 diizinkan 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 memberikan hadiah kepada setiap pelanggan dengan keanggotaan lebih dari satu bulan:

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

Memperpanjang implementasi default

Kode yang Anda tambahkan sejauh ini telah menyediakan implementasi yang nyaman untuk skenario tersebut di mana pengguna menginginkan sesuatu seperti implementasi default, atau untuk memberikan serangkaian aturan yang tidak terkait. Untuk fitur akhir, mari kita lakukan refaktor sedikit pada kode untuk mengaktifkan skenario di mana pengguna mungkin ingin membangun implementasi default.

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

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 mengimplementasikan antarmuka ini, pengambilalihan dapat memanggil metode pembantu statis, dan memperluas logika tersebut untuk memberikan diskon "pelanggan baru":

public decimal ComputeLoyaltyDiscount()
{
   if (PreviousOrders.Any() == false)
        return 0.50m;
    else
        return ICustomer.DefaultLoyaltyDiscount(this);
}

Anda dapat melihat seluruh kode yang sudah jadi di repositori sampel kami di GitHub. Anda bisa mendapatkan aplikasi starter 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. Hal itu membuatnya lebih mudah untuk meningkatkan definisi antarmuka tersebut ketika persyaratan baru ditemukan untuk ide fungsi yang sama.