Dapatkah Anda menjelaskan Prinsip Substitusi Liskov ('L' dari SOLID) dengan contoh C # yang mencakup semua aspek prinsip dengan cara yang disederhanakan? Jika memang mungkin.
c#
.net
oop
solid-principles
liskov-substitution-principle
pencilCake
sumber
sumber
Jawaban:
(Jawaban ini sudah ditulis ulang 2013-05-13, baca pembahasannya di bawah komentar)
LSP adalah tentang mengikuti kontrak kelas dasar.
Anda dapat misalnya tidak membuang pengecualian baru di sub kelas karena yang menggunakan kelas dasar tidak akan mengharapkannya. Hal yang sama berlaku jika kelas dasar melempar
ArgumentNullException
jika ada argumen yang hilang dan sub kelas memungkinkan argumen menjadi null, juga pelanggaran LSP.Berikut adalah contoh struktur kelas yang melanggar LSP:
public interface IDuck { void Swim(); // contract says that IsSwimming should be true if Swim has been called. bool IsSwimming { get; } } public class OrganicDuck : IDuck { public void Swim() { //do something to swim } bool IsSwimming { get { /* return if the duck is swimming */ } } } public class ElectricDuck : IDuck { bool _isSwimming; public void Swim() { if (!IsTurnedOn) return; _isSwimming = true; //swim logic } bool IsSwimming { get { return _isSwimming; } } }
Dan kode panggilannya
void MakeDuckSwim(IDuck duck) { duck.Swim(); }
Seperti yang Anda lihat, ada dua contoh bebek. Satu bebek organik dan satu bebek listrik. Bebek listrik hanya bisa berenang jika dihidupkan. Ini melanggar prinsip LSP karena harus dinyalakan untuk bisa berenang karena
IsSwimming
(yang juga merupakan bagian dari kontrak) tidak akan diatur seperti di kelas dasar.Anda tentu saja dapat menyelesaikannya dengan melakukan hal seperti ini
void MakeDuckSwim(IDuck duck) { if (duck is ElectricDuck) ((ElectricDuck)duck).TurnOn(); duck.Swim(); }
Tapi itu akan merusak prinsip Open / Closed dan harus diterapkan di mana-mana (dan karenanya masih menghasilkan kode yang tidak stabil).
Solusi yang tepat adalah menyalakan bebek secara otomatis dalam
Swim
metode tersebut dan dengan demikian membuat bebek listrik berperilaku persis seperti yang ditentukan olehIDuck
antarmukaMemperbarui
Seseorang menambahkan komentar dan menghapusnya. Itu memiliki poin valid yang ingin saya sampaikan:
Solusi dengan menyalakan bebek di dalam
Swim
metode dapat memiliki efek samping saat bekerja dengan implementasi aktual (ElectricDuck
). Tapi itu bisa diatasi dengan menggunakan implementasi antarmuka eksplisit . imho kemungkinan besar Anda mendapatkan masalah dengan TIDAK menyalakannyaSwim
karena diharapkan itu akan berenang saat menggunakanIDuck
antarmukaPerbarui 2
Diulang beberapa bagian agar lebih jelas.
sumber
if duck is ElectricDuck
bagian itu. Saya memiliki seminar tentang SOLID Kamis lalu :)as
kata kunci, yang sebenarnya menyelamatkan mereka dari banyak pemeriksaan jenis. Saya sedang memikirkan sesuatu seperti berikut:if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
LSP merupakan Pendekatan Praktis
Di mana-mana saya mencari contoh C # LSP, orang telah menggunakan kelas dan antarmuka imajiner. Berikut adalah implementasi praktis LSP yang saya terapkan di salah satu sistem kami.
Skenario: Misalkan kita memiliki 3 database (Nasabah Hipotek, Nasabah Giro dan Nasabah Tabungan) yang menyediakan data nasabah dan kita membutuhkan detail nasabah untuk diberikan nama belakang nasabah. Sekarang kami mungkin mendapatkan lebih dari 1 detail pelanggan dari 3 database tersebut terhadap nama belakang yang diberikan.
Penerapan:
LAPISAN MODEL BISNIS:
public class Customer { // customer detail properties... }
LAYER AKSES DATA:
public interface IDataAccess { Customer GetDetails(string lastName); }
Antarmuka di atas diimplementasikan oleh kelas abstrak
public abstract class BaseDataAccess : IDataAccess { /// <summary> Enterprise library data block Database object. </summary> public Database Database; public Customer GetDetails(string lastName) { // use the database object to call the stored procedure to retrieve the customer details } }
Kelas abstrak ini memiliki metode umum "GetDetails" untuk ketiga database yang diperluas oleh masing-masing kelas database seperti yang ditunjukkan di bawah ini
AKSES DATA PELANGGAN MORTGAGE:
public class MortgageCustomerDataAccess : BaseDataAccess { public MortgageCustomerDataAccess(IDatabaseFactory factory) { this.Database = factory.GetMortgageCustomerDatabase(); } }
AKSES DATA PELANGGAN AKUN SAAT INI:
public class CurrentAccountCustomerDataAccess : BaseDataAccess { public CurrentAccountCustomerDataAccess(IDatabaseFactory factory) { this.Database = factory.GetCurrentAccountCustomerDatabase(); } }
AKSES DATA PELANGGAN REKENING TABUNGAN:
public class SavingsAccountCustomerDataAccess : BaseDataAccess { public SavingsAccountCustomerDataAccess(IDatabaseFactory factory) { this.Database = factory.GetSavingsAccountCustomerDatabase(); } }
Setelah 3 kelas akses data ini ditetapkan, sekarang kami menarik perhatian kami ke klien. Di lapisan Bisnis kami memiliki kelas CustomerServiceManager yang mengembalikan detail pelanggan ke kliennya.
LAPISAN BISNIS:
public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager { public IEnumerable<Customer> GetCustomerDetails(string lastName) { IEnumerable<IDataAccess> dataAccess = new List<IDataAccess>() { new MortgageCustomerDataAccess(new DatabaseFactory()), new CurrentAccountCustomerDataAccess(new DatabaseFactory()), new SavingsAccountCustomerDataAccess(new DatabaseFactory()) }; IList<Customer> customers = new List<Customer>(); foreach (IDataAccess nextDataAccess in dataAccess) { Customer customerDetail = nextDataAccess.GetDetails(lastName); customers.Add(customerDetail); } return customers; } }
Saya belum menunjukkan injeksi ketergantungan agar tetap sederhana karena sekarang sudah semakin rumit.
Sekarang jika kita memiliki database detail pelanggan baru, kita bisa menambahkan kelas baru yang memperluas BaseDataAccess dan menyediakan objek database-nya.
Tentu saja kita membutuhkan prosedur tersimpan yang identik di semua database yang berpartisipasi.
Terakhir, klien untuk
CustomerServiceManager
kelas hanya akan memanggil metode GetCustomerDetails, meneruskan lastName dan tidak peduli tentang bagaimana dan dari mana data itu berasal.Semoga ini akan memberi Anda pendekatan praktis untuk memahami LSP.
sumber
Berikut kode untuk menerapkan Prinsip Pengganti Liskov.
public abstract class Fruit { public abstract string GetColor(); } public class Orange : Fruit { public override string GetColor() { return "Orange Color"; } } public class Apple : Fruit { public override string GetColor() { return "Red color"; } } class Program { static void Main(string[] args) { Fruit fruit = new Orange(); Console.WriteLine(fruit.GetColor()); fruit = new Apple(); Console.WriteLine(fruit.GetColor()); } }
LSV menyatakan: "Kelas turunan harus dapat diganti dengan kelas dasar (atau antarmuka)" & "Metode yang menggunakan referensi ke kelas dasar (atau antarmuka) harus dapat menggunakan metode kelas turunan tanpa mengetahuinya atau mengetahui detailnya . "
sumber