(该答案已于2013-05-13重写,阅读评论底部的讨论)
LSP指的是遵循基类合同的规则。
例如,在子类中抛出新异常是不允许的,因为使用基类的程序不会预期这种情况。如果基类引发ArgumentNullException
异常而子类允许参数为空,则也存在LSP违规问题。
以下是一个违反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; } }
}
而且调用代码
void MakeDuckSwim(IDuck duck)
{
duck.Swim();
}
正如你所看到的,这里有两个鸭子的例子:一个是有机鸭,另一个是电动鸭。只有当电动鸭启动时,它才能游泳。这违反了LSP原则,因为必须启动它才能游泳,而IsSwimming
(也是契约的一部分)不会像基类中那样被设置。
当然,你可以通过像这样做来解决它:
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
但那会违反开闭原则,并且必须在每个地方都实现(因此仍会生成不稳定的代码)。Swim
方法中打开鸭子,从而使电鸭表现出与IDuck
接口定义完全相同的行为。
更新
有人添加了一条评论并将其删除。它提出了一个有效的观点,我想回应一下:Swim
方法内部打开鸭子的解决方法可能会在实际实现(ElectricDuck
)中产生副作用。但可以通过使用 显式接口实现来解决这个问题。在使用IDuck
接口时,期望它会游泳,因此如果不在Swim
中打开它,更可能会出现问题。
更新 2
重新表述了一些部分,以使其更清晰。if duck is ElectricDuck
部分,请再次阅读我的回答。上周我参加了一个关于SOLID的研讨会 :) - jgauffinIUserRepository
,其中包含一个保存方法。在默认存储库中(使用OR/M),保存方法运行良好。但是,当您将其更改为使用WCF服务时,它不适用于所有未序列化的用户对象。这使得应用程序的行为与预期不符。 - jgauffin一个实用的LSP方法
在我寻找LSP的C#示例时,到处都是虚构的类和接口。这里是我在我们系统中实现的LSP的实际应用。
场景:假设我们有三个数据库(抵押贷款客户、活期存款客户和储蓄账户客户)提供客户数据,并且我们需要给定客户的姓氏来获取客户详细信息。现在,我们可能会从这三个数据库中针对给定的姓氏获取多个客户详细信息。
实现:
业务模型层:
public class Customer
{
// customer detail properties...
}
数据访问层:
public interface IDataAccess
{
Customer GetDetails(string lastName);
}
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
}
}
这个抽象类有一个名为“GetDetails”的通用方法,所有3个数据库都可以使用,每个数据库类都像下面展示的那样进行扩展。
房屋抵押客户数据访问:
public class MortgageCustomerDataAccess : BaseDataAccess
{
public MortgageCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetMortgageCustomerDatabase();
}
}
当前账户客户数据访问:
public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetCurrentAccountCustomerDatabase();
}
}
储蓄账户客户数据访问:
public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetSavingsAccountCustomerDatabase();
}
}
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;
}
}
为了让内容更简单易懂,我没有展示依赖注入。现在,如果我们有一个新的客户细节数据库,我们只需要添加一个扩展BaseDataAccess类并提供其数据库对象的新类。
当然,在所有参与的数据库中,我们需要相同的存储过程。
最后,CustomerServiceManager
类的客户端只会调用GetCustomerDetails方法,传递lastName参数,不关心数据来自何处以及如何获取。
希望这会给你带来一种实际的方法来理解LSP(里氏替换原则)。
这是应用里氏替换原则的代码。
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原则: “派生类应该能够替换其基类(或接口)” 和 “使用基类(或接口)引用的方法必须能够使用派生类的方法,而不需要知道细节或了解它。”
橙色
和红色
。如果你需要在VSCode中测试这段代码,请查看以下链接:https://code.visualstudio.com/docs/languages/csharp 和 https://channel9.msdn.com/Blogs/dotnet/Get-started-VSCode-Csharp-NET-Core-Windows - carloswm85