Bạn có thể giải thích Nguyên tắc thay thế Liskov (Chữ 'L' của SOLID) bằng một ví dụ C # hay bao gồm tất cả các khía cạnh của nguyên tắc một cách đơn giản không? Nếu nó thực sự có thể.
Bạn có thể giải thích Nguyên tắc thay thế Liskov (Chữ 'L' của SOLID) bằng một ví dụ C # hay bao gồm tất cả các khía cạnh của nguyên tắc một cách đơn giản không? Nếu nó thực sự có thể.
Câu trả lời:
(Câu trả lời này đã được viết lại 2013-05-13, hãy đọc phần thảo luận ở phần dưới cùng của ý kiến)
LSP là về việc tuân theo hợp đồng của lớp cơ sở.
Ví dụ, bạn có thể không ném các ngoại lệ mới trong các lớp con vì lớp sử dụng lớp cơ sở sẽ không mong đợi điều đó. Tương tự đối với trường hợp lớp cơ sở ném ra ArgumentNullException
nếu một đối số bị thiếu và lớp con cho phép đối số trống, cũng là một vi phạm LSP.
Đây là một ví dụ về cấu trúc lớp vi phạm 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; } }
}
Và mã gọi
void MakeDuckSwim(IDuck duck)
{
duck.Swim();
}
Như bạn có thể thấy, có hai ví dụ về vịt. Một con vịt hữu cơ và một con vịt điện. Vịt điện chỉ có thể bơi nếu nó được bật. Điều này phá vỡ nguyên tắc LSP vì nó phải được bật để có thể bơi vì IsSwimming
(cũng là một phần của hợp đồng) sẽ không được đặt như trong lớp cơ sở.
Tất nhiên bạn có thể giải quyết nó bằng cách làm như thế này
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
Nhưng điều đó sẽ phá vỡ nguyên tắc Mở / Đóng và phải được thực hiện ở mọi nơi (và do đó vẫn tạo ra mã không ổn định).
Giải pháp thích hợp sẽ là tự động bật vịt trong Swim
phương pháp này và bằng cách làm như vậy, vịt điện hoạt động chính xác như được xác định bởi IDuck
giao diện
Cập nhật
Ai đó đã thêm nhận xét và xóa nó. Nó có một điểm hợp lệ mà tôi muốn giải quyết:
Giải pháp quay vịt bên trong Swim
phương pháp có thể có tác dụng phụ khi làm việc với việc triển khai thực tế ( ElectricDuck
). Nhưng điều đó có thể được giải quyết bằng cách sử dụng triển khai giao diện rõ ràng . imho có nhiều khả năng bạn gặp sự cố bằng cách KHÔNG bật nó lên Swim
vì có thể nó sẽ bơi khi sử dụng IDuck
giao diện
Cập nhật 2
Diễn đạt lại một số phần để làm rõ hơn.
if duck is ElectricDuck
phần này. Tôi đã có một cuộc hội thảo về RẮN thứ Năm tuần trước :)
as
từ khóa, điều này thực sự giúp họ không phải kiểm tra nhiều kiểu. Tôi đang nghĩ điều gì đó như sau: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 một phương pháp tiếp cận thực tế
Ở mọi nơi tôi tìm kiếm các ví dụ C # của LSP, mọi người đã sử dụng các lớp và giao diện tưởng tượng. Đây là cách triển khai thực tế của LSP mà tôi đã triển khai trong một trong các hệ thống của chúng tôi.
Tình huống: Giả sử chúng ta có 3 cơ sở dữ liệu (Khách hàng thế chấp, Khách hàng Tài khoản vãng lai và Khách hàng Tài khoản Tiết kiệm) cung cấp dữ liệu khách hàng và chúng tôi cần thông tin chi tiết về họ của khách hàng. Bây giờ chúng tôi có thể nhận được nhiều hơn 1 thông tin chi tiết về khách hàng từ 3 cơ sở dữ liệu đó dựa trên họ đã cho.
Thực hiện:
TẦNG MÔ HÌNH KINH DOANH:
public class Customer
{
// customer detail properties...
}
LỚP TRUY CẬP DỮ LIỆU:
public interface IDataAccess
{
Customer GetDetails(string lastName);
}
Giao diện trên được thực hiện bởi lớp trừu tượng
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
}
}
Lớp trừu tượng này có một phương thức chung "GetDetails" cho cả 3 cơ sở dữ liệu được mở rộng bởi từng lớp cơ sở dữ liệu như hình dưới đây
THẾ CHẤP TRUY CẬP DỮ LIỆU KHÁCH HÀNG:
public class MortgageCustomerDataAccess : BaseDataAccess
{
public MortgageCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetMortgageCustomerDatabase();
}
}
TRUY CẬP DỮ LIỆU KHÁCH HÀNG TÀI KHOẢN HIỆN TẠI:
public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetCurrentAccountCustomerDatabase();
}
}
TIẾT KIỆM TÀI KHOẢN TRUY CẬP DỮ LIỆU KHÁCH HÀNG:
public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetSavingsAccountCustomerDatabase();
}
}
Sau khi 3 lớp truy cập dữ liệu này được thiết lập, bây giờ chúng tôi hướng sự chú ý của chúng tôi đến máy khách. Trong lớp Kinh doanh, chúng ta có lớp CustomerServiceManager trả về chi tiết khách hàng cho các khách hàng của nó.
TẦNG KINH DOANH:
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;
}
}
Tôi đã không hiển thị việc tiêm phụ thuộc để giữ cho nó đơn giản vì bây giờ nó đã trở nên phức tạp.
Bây giờ nếu chúng ta có một cơ sở dữ liệu chi tiết về khách hàng mới, chúng ta có thể thêm một lớp mới mở rộng BaseDataAccess và cung cấp đối tượng cơ sở dữ liệu của nó.
Tất nhiên chúng ta cần các thủ tục được lưu trữ giống hệt nhau trong tất cả các cơ sở dữ liệu tham gia.
Cuối cùng, máy khách cho CustomerServiceManager
lớp sẽ chỉ gọi phương thức GetCustomerDetails, truyền lastName và không quan tâm đến cách thức và nguồn dữ liệu đến từ đâu.
Hy vọng điều này sẽ cung cấp cho bạn một cách tiếp cận thực tế để hiểu LSP.
Đây là mã để áp dụng Nguyên tắc thay thế 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 tuyên bố: "Các lớp dẫn xuất phải có thể thay thế cho các lớp cơ sở (hoặc giao diện) của chúng" & "Các phương thức sử dụng tham chiếu đến các lớp cơ sở (hoặc giao diện) phải có thể sử dụng các phương thức của các lớp dẫn xuất mà không cần biết về nó hoặc biết chi tiết . "