Situasi saat ini
Pengaturan saat ini melanggar Prinsip Segregasi Antarmuka (I dalam SOLID).
Referensi
Menurut Wikipedia prinsip pemisahan antarmuka (ISP) menyatakan bahwa tidak ada klien yang harus dipaksa untuk bergantung pada metode yang tidak digunakannya . Prinsip pemisahan antarmuka dirumuskan oleh Robert Martin pada pertengahan 1990-an.
Dengan kata lain, jika ini antarmuka Anda:
public interface IUserBackend
{
User getUser(int uid);
User createUser(int uid);
void deleteUser(int uid);
void setPassword(int uid, string password);
}
Maka setiap kelas yang mengimplementasikan antarmuka ini harus memanfaatkan setiap metode antarmuka yang terdaftar. Tanpa pengecualian.
Bayangkan jika ada metode umum:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
backendService.deleteUser(user.Uid);
}
Jika Anda benar-benar membuatnya sehingga hanya beberapa kelas implementasi yang benar-benar dapat menghapus pengguna, maka metode ini kadang-kadang akan meledak di wajah Anda (atau tidak melakukan apa-apa sama sekali). Itu bukan desain yang bagus.
Solusi yang Anda usulkan
Saya telah melihat solusi di mana IUserInterface memiliki metode implementActions yang mengembalikan integer yang merupakan hasil dari bitwise OR dari tindakan bitwise ANDed dengan tindakan yang diminta.
Apa yang pada dasarnya ingin Anda lakukan adalah:
public void HaveUserDeleted(IUserBackend backendService, User user)
{
if(backendService.canDeleteUser())
backendService.deleteUser(user.Uid);
}
Saya mengabaikan bagaimana tepatnya kami menentukan apakah kelas yang diberikan dapat menghapus pengguna. Apakah itu boolean, sedikit bendera, ... tidak masalah. Semuanya bermuara pada jawaban biner: dapatkah menghapus pengguna, ya atau tidak?
Itu akan menyelesaikan masalah, kan? Secara teknis, ya. Tapi sekarang, Anda melanggar Prinsip Pergantian Liskov (L dalam SOLID).
Meninggalkan penjelasan Wikipedia yang agak rumit, saya menemukan contoh yang layak di StackOverflow . Perhatikan contoh "buruk":
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
Saya berasumsi Anda melihat kesamaan di sini. Ini adalah metode yang seharusnya menangani objek yang diabstraksi ( IDuck, IUserBackend), tetapi karena desain kelas yang dikompromikan, ia terpaksa menangani implementasi spesifik terlebih dahulu ( ElectricDuck, pastikan itu bukan IUserBackendkelas yang tidak dapat menghapus pengguna).
Ini mengalahkan tujuan mengembangkan pendekatan abstrak.
Catatan: Contoh di sini lebih mudah untuk diperbaiki daripada kasing Anda. Misalnya, itu sudah cukup untuk memiliki ElectricDuckturn sendiri di dalam yang Swim()metode. Kedua itik masih bisa berenang, sehingga hasil fungsionalnya sama.
Anda mungkin ingin melakukan hal serupa. Jangan . Anda tidak bisa hanya berpura - pura menghapus pengguna tetapi pada kenyataannya memiliki badan metode yang kosong. Meskipun ini berfungsi dari perspektif teknis, tidak mungkin untuk mengetahui apakah kelas pelaksana Anda benar-benar akan melakukan sesuatu ketika diminta untuk melakukan sesuatu. Itu adalah tempat berkembang biak untuk kode yang tidak dapat dipelihara.
Solusi yang saya usulkan
Tetapi Anda mengatakan bahwa itu mungkin (dan benar) untuk kelas implementasi hanya menangani beberapa metode ini.
Sebagai contoh, katakanlah untuk setiap kemungkinan kombinasi dari metode ini, ada kelas yang akan mengimplementasikannya. Ini mencakup semua pangkalan kami.
Solusinya di sini adalah dengan membagi antarmuka .
public interface IGetUserService
{
User getUser(int uid);
}
public interface ICreateUserService
{
User createUser(int uid);
}
public interface IDeleteUserService
{
void deleteUser(int uid);
}
public interface ISetPasswordService
{
void setPassword(int uid, string password);
}
Perhatikan bahwa Anda bisa melihat ini pada awal jawaban saya. Nama Prinsip Segregasi Antarmuka telah mengungkapkan bahwa prinsip ini dirancang untuk membuat Anda memisahkan antarmuka hingga tingkat yang memadai.
Ini memungkinkan Anda untuk mencampur dan mencocokkan antarmuka sesuka Anda:
public class UserRetrievalService
: IGetUserService, ICreateUserService
{
//getUser and createUser methods implemented here
}
public class UserDeleteService
: IDeleteUserService
{
//deleteUser method implemented here
}
public class DoesEverythingService
: IGetUserService, ICreateUserService, IDeleteUserService, ISetPasswordService
{
//All methods implemented here
}
Setiap kelas dapat memutuskan yang ingin mereka lakukan, tanpa harus melanggar kontrak antarmuka mereka.
Ini juga berarti bahwa kita tidak perlu memeriksa apakah kelas tertentu dapat menghapus pengguna. Setiap kelas yang mengimplementasikan IDeleteUserServiceantarmuka akan dapat menghapus pengguna = Tidak ada pelanggaran Prinsip Pergantian Liskov .
public void HaveUserDeleted(IDeleteUserService backendService, User user)
{
backendService.deleteUser(user.Uid); //guaranteed to work
}
Jika ada yang mencoba meneruskan objek yang tidak diimplementasikan IDeleteUserService, program akan menolak untuk dikompilasi. Inilah mengapa kami suka memiliki keamanan jenis.
HaveUserDeleted(new DoesEverythingService()); // No problem.
HaveUserDeleted(new UserDeleteService()); // No problem.
HaveUserDeleted(new UserRetrievalService()); // COMPILE ERROR
Catatan kaki
Saya mengambil contohnya dengan ekstrem, memisahkan antarmuka menjadi potongan terkecil yang mungkin. Namun, jika situasinya berbeda, Anda bisa lolos dengan potongan yang lebih besar.
Misalnya, jika setiap layanan yang dapat membuat pengguna selalu mampu menghapus pengguna (dan sebaliknya), Anda dapat menjaga metode ini sebagai bagian dari satu antarmuka:
public interface IManageUserService
{
User createUser(int uid);
void deleteUser(int uid);
}
Tidak ada manfaat teknis melakukan hal ini daripada memisahkan ke potongan yang lebih kecil; tetapi itu akan membuat pengembangan sedikit lebih mudah karena membutuhkan lebih sedikit boilerplating.
IUserBackendtidak harus berisideleteUsermetode sama sekali. Itu harus menjadi bagian dariIUserDeleteBackend(atau apa pun yang Anda ingin menyebutnya). Kode yang perlu dihapus pengguna akan memiliki argumenIUserDeleteBackend, kode yang tidak memerlukan fungsionalitas yang akan digunakanIUserBackenddan tidak akan memiliki masalah dengan metode yang tidak diterapkan.