我听说利斯科夫替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用例子是什么?
当前回答
以Board数组的形式实现ThreeDBoard会有用吗?
也许你想把不同平面上的ThreeDBoard切片作为一个板。在这种情况下,您可能希望为Board抽象出一个接口(或抽象类),以允许多种实现。
就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard提取一个Board接口(尽管上述方法都不适合)。
其他回答
这里有一个清单来确定你是否违反了利斯科夫法则。
如果你违反了以下项目之一->,你违反了里斯科夫。 如果你不违反任何->不能得出任何结论。
检查表:
No new exceptions should be thrown in derived class: If your base class threw ArgumentNullException then your sub classes were only allowed to throw exceptions of type ArgumentNullException or any exceptions derived from ArgumentNullException. Throwing IndexOutOfRangeException is a violation of Liskov. Pre-conditions cannot be strengthened: Assume your base class works with a member int. Now your sub-type requires that int to be positive. This is strengthened pre-conditions, and now any code that worked perfectly fine before with negative ints is broken. Post-conditions cannot be weakened: Assume your base class required all connections to the database should be closed before the method returned. In your sub-class you overrode that method and left the connection open for further reuse. You have weakened the post-conditions of that method. Invariants must be preserved: The most difficult and painful constraint to fulfill. Invariants are sometimes hidden in the base class and the only way to reveal them is to read the code of the base class. Basically you have to be sure when you override a method anything unchangeable must remain unchanged after your overridden method is executed. The best thing I can think of is to enforce these invariant constraints in the base class but that would not be easy. History Constraint: When overriding a method you are not allowed to modify an unmodifiable property in the base class. Take a look at these code and you can see Name is defined to be unmodifiable (private set) but SubType introduces new method that allows modifying it (through reflection): public class SuperType { public string Name { get; private set; } public SuperType(string name, int age) { Name = name; Age = age; } } public class SubType : SuperType { public void ChangeName(string newName) { var propertyType = base.GetType().GetProperty("Name").SetValue(this, newName); } }
还有2项:方法参数的逆变性和返回类型的协方差。但这在c#中是不可能的(我是c#开发人员),所以我不关心它们。
以Board数组的形式实现ThreeDBoard会有用吗?
也许你想把不同平面上的ThreeDBoard切片作为一个板。在这种情况下,您可能希望为Board抽象出一个接口(或抽象类),以允许多种实现。
就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard提取一个Board接口(尽管上述方法都不适合)。
利斯科夫替换原理
被重写的方法不应该保持为空 被重写的方法不应该抛出错误 基类或接口行为不应该因为派生类行为而进行修改(重做)。
LSP说“对象应该被它们的子类型替换”。 另一方面,这一原则指向
子类永远不应该破坏父类的类型定义。
通过以下示例,可以更好地理解LSP。
没有太阳能发电:
public interface CustomerLayout{
public void render();
}
public FreeCustomer implements CustomerLayout {
...
@Override
public void render(){
//code
}
}
public PremiumCustomer implements CustomerLayout{
...
@Override
public void render(){
if(!hasSeenAd)
return; //it isn`t rendered in this case
//code
}
}
public void renderView(CustomerLayout layout){
layout.render();
}
LSP修复:
public interface CustomerLayout{
public void render();
}
public FreeCustomer implements CustomerLayout {
...
@Override
public void render(){
//code
}
}
public PremiumCustomer implements CustomerLayout{
...
@Override
public void render(){
if(!hasSeenAd)
showAd();//it has a specific behavior based on its requirement
//code
}
}
public void renderView(CustomerLayout layout){
layout.render();
}
Liskov's Substitution Principle(LSP) All the time we design a program module and we create some class hierarchies. Then we extend some classes creating some derived classes. We must make sure that the new derived classes just extend without replacing the functionality of old classes. Otherwise, the new classes can produce undesired effects when they are used in existing program modules. Liskov's Substitution Principle states that if a program module is using a Base class, then the reference to the Base class can be replaced with a Derived class without affecting the functionality of the program module.
例子:
Below is the classic example for which the Liskov's Substitution Principle is violated. In the example, 2 classes are used: Rectangle and Square. Let's assume that the Rectangle object is used somewhere in the application. We extend the application and add the Square class. The square class is returned by a factory pattern, based on some conditions and we don't know the exact what type of object will be returned. But we know it's a Rectangle. We get the rectangle object, set the width to 5 and height to 10 and get the area. For a rectangle with width 5 and height 10, the area should be 50. Instead, the result will be 100
// Violation of Likov's Substitution Principle
class Rectangle {
protected int m_width;
protected int m_height;
public void setWidth(int width) {
m_width = width;
}
public void setHeight(int height) {
m_height = height;
}
public int getWidth() {
return m_width;
}
public int getHeight() {
return m_height;
}
public int getArea() {
return m_width * m_height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
m_width = width;
m_height = width;
}
public void setHeight(int height) {
m_width = height;
m_height = height;
}
}
class LspTest {
private static Rectangle getNewRectangle() {
// it can be an object returned by some factory ...
return new Square();
}
public static void main(String args[]) {
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);
r.setHeight(10);
// user knows that r it's a rectangle.
// It assumes that he's able to set the width and height as for the base
// class
System.out.println(r.getArea());
// now he's surprised to see that the area is 100 instead of 50.
}
}
结论: 这个原则只是开闭原则的延伸 意味着我们必须确保新的派生类正在扩展 基类而不改变它们的行为。
参见:开闭原则
对于更好的结构,还有一些类似的概念:约定优于配置