Answers:
(此答案已被改写为2013-05-13,请阅读评论底部的讨论)
LSP是关于遵循基类的合同的。
例如,您不能在子类中引发新的异常,因为使用基类的异常不会发生这种情况。如果基类ArgumentNullException
在缺少参数时抛出该异常,并且子类允许该参数为null(同样违反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
。但这可以通过使用显式接口实现来解决。恕我直言,您很有可能通过不打开电源来解决问题,Swim
因为使用该IDuck
界面时,它会游动
更新2
改写一些部分以使其更清晰。
if duck is ElectricDuck
零件,请再次阅读我的答案。上周四,我参加了有关SOLID的研讨会:)
as
关键字,这实际上使他们免于进行大量类型检查。我在想以下问题: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实用方法
我到处都在寻找LSP的C#示例,人们使用了虚构的类和接口。这是我在我们的系统之一中实现的LSP的实际实现。
方案:假设我们有3个数据库(抵押客户,活期账户客户和储蓄账户客户)提供客户数据,并且我们需要给定客户姓氏的客户详细信息。现在我们可以根据给定的姓氏从这3个数据库中获得1个以上的客户详细信息。
实现方式:
业务模型层:
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
}
}
这个抽象类对所有3个数据库都有一个通用方法“ GetDetails”,每个数据库类都对其进行了扩展,如下所示
抵押客户数据访问:
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();
}
}
一旦设置了这三个数据访问类,现在我们将注意力吸引到客户端。在业务层中,我们具有CustomerServiceManager类,该类将客户详细信息返回给其客户。
业务层:
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的实用方法。
这是应用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指出:“派生类应该可以替换其基类(或接口)”和“使用对基类(或接口)的引用的方法必须能够使用派生类的方法,而无需对其进行了解或不了解细节。”