问候,
本周的技巧介绍了有关面向对象的一些理论
编程。 首先,我们引入一个示例项目:一个简单的几何
项目。 我们要定义一堆代表可调整大小的形状的类。
没什么复杂的:我们定义一个抽象的Shape类,然后开始:
public abstract class Shape {
abstract public double getArea();
...
}
public class Rectangle extends Shape {
// it's dimensions
private double width, height;
// contructor
public Rectangle(double width, double height) {
this.width= width;
this.height= height;
}
// getters and setters:
public double getWidth() { return width; }
public double getHeight() { return height; }
public void setWidth(double width) { this.width= width; }
public void setHeight(double height) { this.height= height; }
// get the area
public double getArea() { return width*height; }
}
抽象形状类不知道如何计算面积,因此该方法
被抽象化。 矩形定义此方法。 因为矩形*是*
形状,我们从抽象类的形状扩展。 这个小场景可以是
几乎可以在任何初学者的教科书中找到; 看着这几乎无聊
在最紧急的时间。
我们通过定义一个Square完成这个无聊的小项目。 众所周知,广场
* is-a *是Rectangle的特殊类型,因此我们扩展了Rectangle类:
public class Square extends Rectangle {
// constructor
public Square(double side) { super(side, side); }
// getters and setters:
public double getSide() { return getWidth(); } // or getHeight()
public void setSide(double side) {
super.setWidth(side);
super.setHeight(side);
}
public void setWidth(double width) { this.setSide(side); }
public void setHeight(double width) { this.setSide(side); }
}
在那里:完成。
我们使用了大多数Rectangle代码:我们甚至不必
定义一个新的getArea()方法,一切都很好。 我们把所有东西都包好
构建一个.jar文件,将其刻录到CD上(我们甚至在其上贴上一个漂亮的标签),然后发送
CD和账单给我们的客户,我们将享受周末。
当天,我们的客户收到CD,安装了所有东西,
因为他是一个讨厌的nitpicker,所以他想测试一下:
public static void main(String[] args) {
Rectangle r= new Rectangle(1.0, 0.0);
for (int i= 1; i <= 10; i++) {
r.setHeight(i);
if (r.getArea() != i)
System.err.println("This software is not correct!);
}
}
那天晚些时候,我们的客户给我们打了个电话,告诉我们测试已经
到目前为止还算不错。 我们知道我们的软件很好,我们向我们表示祝贺
客户使用他的新的,正确的正确软件。
我们的客户还知道Square * is-a *矩形的特殊类型,因此他
制作他的下一个测试:
public static void main(String[] args) {
Rectangle r= new Square(1.0);
for (int i= 1; i <= 10; i++) {
r.setHeight(i);
if (r.getArea() != i)
System.err.println("This software is not correct!);
}
}
你猜怎么了?
当天晚些时候,我们的客户再次致电:他很生气;
九分
十次测试运行失败; 只是单元平方测试成功。 他所做的只是
实例化一个Square而不是Rectangle,并且因为Square * is-a *
矩形应该可以正常工作,但不能正常工作。
我们决定暂时跳过该帐单,然后再看看我们的
类; 周末去了。
在午夜到来之前,我们意识到我们走错了路:
无论我们如何保持宽度和高度都相等,
如果我们认为Square是特殊的,则getArea()会给出意外结果
矩形。 我们也不能这样做:
public class Square extends Rectangle {
...
public void setWidth(double width) {
throw IllegalArgumentException("Don't use this method for Squares!");
}
public void setHeight(double height) {
throw IllegalArgumentException("Don't use this method for Squares!");
}
}
这种黑客肯定不会通过我们客户的测试套件。
但是一个正方形
*是*一种特殊的矩形。 还是?
1988年,巴拉巴拉·利斯科夫(Barabara Liskov)对这个问题进行了以下观察:
“这里需要的是类似以下替换属性的内容:如果对于类型D的每个对象o2,都有一个类型B的对象o1,使得
所有程序P均以B定义,当o2时P的行为不变
被o1取代,那么D可以是B的子类型。”
-数据抽象和层次结构,SIGPLAN通告,23,5(1988年5月)。
巴拉巴拉·里斯科夫(Barabara Liskov)是数学类型,数学家喜欢
希腊文:您给他们一个问题,他们将其翻译为自己的语言
突然之间,您不再了解自己的问题。 (*)
对于可能穿网球鞋或偶尔穿Python皮靴子的普通人来说,
她这样说:程序P是我们客户的测试套件(请参见上文)。 o1
object是Rectangle类中的对象。 我们客户的测试套件有效
和他们在一起很好(还记得第一个快乐的电话吗?)
是派生类(来自类D的对象o2,在我们的示例中为正方形)
该程序应该可以继续正常运行。
这一切都很好,但我们的Square类(扩展了Rectangle)
没有通过此替代测试。 这就是巴拉巴拉·里斯科夫(Barabara Liskov)的LSP
都是关于
:如果派生类未通过此测试,则不应为派生类。
但是再说一遍:正方形* is-a *矩形。 从数学上讲是的,
但在我们可以调整这些内容大小的示例中,“正方形”不是“矩形”。
* is-a *关系实际上不是数学关系。 这是一种行为
关系和我们的尝试在LSP测试驱动器中惨败。
该程序P期望Rectangle继续运行,无论是否
它们是“特殊”矩形。 如果在矩形上运行正常,则应该运行良好
一个特殊的矩形。 如果运行不正常,则特殊的Rectangle不是
应该完全是Rectangle,而不应该是
矩形类。 这就是LSP的全部意义。 这是一种石蕊测试
对于您的继承树:如果子类无法充当其父类:
不要让班级成为那个父母的孩子。 (通常不起作用
在现实世界中的方式;-)
最后,这是我们重新设计的Square类:
public class Square extends Shape {
private double side;
// constructor:
public Square(double side) { this.side= side; }
// getters and setters:
public double getSide() { return side; }
public void setSide(double side) { this.side= side; }
// the area implementation:
public double getArea() { return side*side; }
}
是无聊的吗?
但是现在它通过了LSP!
还剩下一件事:芭芭拉·利斯科夫(Barbara Liskov)为什么写“那么它可以是……
子类”。Java的答案很简单:如果两个类实现相同的
接口,并且程序P需要该接口,它应该运行良好
无论同一个的不同实现(类)中的哪个
接口被传递到该程序P。
LSP制定后不久,Bertrand Meyers提出了
与LSP接壤的“按合同设计”概念。 每种方法都有
前提条件和后置条件。 调用方法时,其前提条件
必须持有; 方法完成时,它承诺其后置条件成立。
例如,Rectangle中setWidth方法的后置条件
类,是:
this.width ==宽度&& this.heigth == oldHeight
其中oldHeight表示调用该方法之前的高度值。
正如Meyers所说:
“重写方法(在派生类中)时,您只能替换以弱者为前提的先决条件,以强者为条件替换后置条件
“将超类方法的条件” 。
如果超类中的“更强”前提,则“更弱”前提为真
是真的。 换句话说,当通过基本类型使用对象时,用户
(LSP中的程序P)仅知道...的先决条件和后置条件。
基类。 因此,派生对象一定不能期望此类用户服从
前提条件要比基类要求的条件强。 那是,
他们必须接受基类可以接受的任何东西。 另外,派生
类必须符合基础的所有后置条件,即它们的行为
并且输出(副作用)不得违反基类施加的约束。
可以清楚地看到,矩形/正方形示例违反了“更强
Square条件中的后置条件”,即上面显示的后置条件
矩形类在派生的Square类中不成立。
直到下一次将是“播放时间”:数独解算器及所有其他功能。
亲切的问候,
乔斯
From: https://bytes.com/topic/java/insights/642579-lsp-programming-contract