Mesin kios di gerai makanan datang dalam beberapa varian: kios penuh yang menerima pesanan, memproses pembayaran, dan mencetak struk; kios informasi yang hanya menampilkan menu. Kebutuhan keduanya berbeda, tetapi pengembang awal sering menggabungkan semua kemampuan ke dalam satu interface agar terasa praktis. Keputusan itu menimbulkan masalah yang dibahas bab ini.
Masalah pada Kontrak yang Menggabungkan Semuanya #
public interface IMesinKios
{
void PilihMenu(string menu);
void ProsesPembayaran(int jumlah);
void CetakStruk(int jumlah);
}
public class KiosMenu : IMesinKios
{
public void PilihMenu(string menu)
{
Console.WriteLine($"Menu {menu} dipilih.");
}
public void ProsesPembayaran(int jumlah)
{
throw new NotImplementedException();
}
public void CetakStruk(int jumlah)
{
throw new NotImplementedException();
}
}Class KiosMenu tidak mampu memproses pembayaran dan tidak memiliki printer, tetapi kontrak tetap memaksa kedua method itu diisi. Isian berupa throw new NotImplementedException() hanya menunda persoalan: pemanggil yang kurang teliti masih bisa memanggil method yang tidak pernah berjalan. Masalah ini juga tidak berhenti saat class ditulis; setiap kemampuan baru yang ditambahkan ke IMesinKios wajib diisi pula oleh KiosMenu pada iterasi berikutnya. Setiap member tambahan pada interface membebani seluruh class yang mengimplementasikannya, termasuk class yang tidak membutuhkan member tersebut.
Memecah Kontrak Sesuai Kemampuan #
Solusinya adalah memecah kontrak menjadi bagian kecil yang berdiri sendiri. Kontrak pembayaran sudah dijanjikan IPembayaran pada bab-bab sebelumnya, sehingga kita cukup menambah dua kontrak baru:
public interface IMemilih
{
void PilihMenu(string menu);
}
public interface ICetakStruk
{
void CetakStruk(int jumlah);
}
public class KiosKasir : IMemilih, IPembayaran, ICetakStruk
{
public int BiayaAdmin => 0;
public void PilihMenu(string menu)
{
Console.WriteLine($"Menu {menu} dipilih.");
}
public void ProsesPembayaran(int jumlah)
{
Console.WriteLine($"Pembayaran Rp{jumlah} diproses di kios.");
}
public void CetakStruk(int jumlah)
{
Console.WriteLine($"Struk Rp{jumlah} dicetak.");
}
}KiosKasir menanggung tiga kontrak sekaligus karena memang memiliki ketiga kemampuan. Sebaliknya, KiosMenu cukup menyatakan IMemilih saja dan tidak lagi mengisi method yang tidak mampu dijalankannya.
Memakai Kontrak Kecil di Pemanggil #
IMemilih kios = new KiosKasir();
kios.PilihMenu("Ayam Geprek");
if (kios is IPembayaran pembayaran)
{
pembayaran.ProsesPembayaran(25000);
}
if (kios is ICetakStruk struk)
{
struk.CetakStruk(25000);
}Output yang Dihasilkan:
Menu Ayam Geprek dipilih.
Pembayaran Rp25000 diproses di kios.
Struk Rp25000 dicetak.Mari kita bedah baris demi baris:
IMemilih kios = new KiosKasir();— variabel dideklarasikan dengan kontrak terkecil yang dibutuhkan pemanggil, sehingga member lain tidak terlihat di posisi ini.kios is IPembayaran pembayaran— operatorismenguji apakah object juga menepati kontrak pembayaran sebelum membernya dipanggil.kios is ICetakStruk struk— pengujian yang sama diterapkan pada kontrak cetak struk.
Dengan kontrak yang kecil, pemanggil dapat memeriksa kemampuan object sebelum memakainya, dan setiap class hanya menanggung kemampuan yang benar-benar dimilikinya.