Java 为什么不可变对象允许遵循Liskov替换原则?
我想展示一个矩形和正方形的示例:Java 为什么不可变对象允许遵循Liskov替换原则?,java,oop,solid-principles,Java,Oop,Solid Principles,我想展示一个矩形和正方形的示例: class Rectangle { private int width; private int height; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHe
class Rectangle {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int area() {
return width * height;
}
}
class Square extends Rectangle{
@Override
public void setWidth(int width){
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height){
super.setHeight(height);
super.setWidth(height);
}
}
public class Use {
public static void main(String[] args) {
Rectangle sq = new Square();
LSPTest(sq);
}
public static void LSPTest(Rectangle rec) {
rec.setWidth(5);
rec.setHeight(4);
if (rec.area() == 20) {
// do Something
}
}
}
如果我在方法LSPTest中替换了正方形
的一个实例而不是矩形
,我的程序的行为将会改变。这与LSP相反
我听说,那个不可变的对象可以解决这个问题。但是为什么呢
我换了一个例子。
我在矩形中添加构造函数
:
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
然后,我换了二传手:
public Rectangle setWidth(int width) {
return new Rectangle(width, this.height);
}
public Rectangle setHeight(int height) {
return new Rectangle(this.width, height);
}
现在,Square
看起来像:
class Square{
public Square() {
}
public Square(int width, int height) {
super(width, height);
}
@Override
public Rectangle setWidth(int width) {
return new Rectangle(width, width);
}
@Override
public Rectangle setHeight(int height) {
return new Rectangle(height, height);
}
}
以下是客户端代码:
public class Use {
public static void main(String[] args) {
Rectangle sq = new Square(4, 4);
LSPTest(sq);
}
public static void LSPTest(Rectangle rec) {
rec = rec.setHeight(5);
if (rec.area() == 20) {
System.out.println("yes");
}
}
}
同样的问题依然存在。更改对象本身还是返回新对象有什么区别。对于基类和子类,程序的行为仍然不同。我抓住了这些引语(我的重点):
假设在矩形
基类上有SetWidth
和SetHeight
方法;这似乎完全合乎逻辑。但是,如果您的矩形
引用指向一个正方形
,则设置宽度
和设置高度
没有意义,因为设置一个会改变另一个以匹配它。在这种情况下,Square
未能通过使用Rectangle
的Liskov替换测试,并且从Rectangle
继承Square
的抽象是一个坏的抽象
……还有
LSP指出的是子类型行为应该与基类型规范中定义的基类型行为相匹配。如果矩形基本类型规范说高度和宽度可以独立设置,那么LSP说正方形不能是矩形的子类型如果矩形规范说矩形是不可变的,那么正方形可以是矩形的子类型。这都是关于子类型维护为基类型指定的行为
我想如果你有这样一个构造器,它会工作的:
Square(int side){
super(side,side);
...
}
因为没有办法改变不变的东西,所以没有设置器。正方形永远是正方形
但两者之间应该有一种不违反LSP的关系,这种关系也不会强迫您使用不可变对象。我们只是做错了
在数学中,正方形可以看作是矩形的一种。事实上,它是一种更为特殊的矩形。天真地说,这样做似乎合乎逻辑,因为矩形看起来很超级。但是拥有子类的目的不是为了创建现有类的较弱版本,而是为了增强功能
为什么不做一些像:
class Square{
void setSides(int side);
Boundary getSides();
}
class Rectangle extends Square{
//Overload
void setSides(int width, int height);
@Override
Boundary getSides();
}
我还要指出,setter是用于设置的。下面的代码很糟糕,因为您实际上创建了一个方法,它不会执行它所说的操作
public Rectangle setWidth(int width) {
return new Rectangle(width, this.height);
}
我抓住了这些引语(强调我的):
假设在矩形
基类上有SetWidth
和SetHeight
方法;这似乎完全合乎逻辑。但是,如果您的矩形
引用指向一个正方形
,则设置宽度
和设置高度
没有意义,因为设置一个会改变另一个以匹配它。在这种情况下,Square
未能通过使用Rectangle
的Liskov替换测试,并且从Rectangle
继承Square
的抽象是一个坏的抽象
……还有
LSP指出的是子类型行为应该与基类型规范中定义的基类型行为相匹配。如果矩形基本类型规范说高度和宽度可以独立设置,那么LSP说正方形不能是矩形的子类型如果矩形规范说矩形是不可变的,那么正方形可以是矩形的子类型。这都是关于子类型维护为基类型指定的行为
我想如果你有这样一个构造器,它会工作的:
Square(int side){
super(side,side);
...
}
因为没有办法改变不变的东西,所以没有设置器。正方形永远是正方形
但两者之间应该有一种不违反LSP的关系,这种关系也不会强迫您使用不可变对象。我们只是做错了
在数学中,正方形可以看作是矩形的一种。事实上,它是一种更为特殊的矩形。天真地说,这样做似乎合乎逻辑,因为矩形看起来很超级。但是拥有子类的目的不是为了创建现有类的较弱版本,而是为了增强功能
为什么不做一些像:
class Square{
void setSides(int side);
Boundary getSides();
}
class Rectangle extends Square{
//Overload
void setSides(int width, int height);
@Override
Boundary getSides();
}
我还要指出,setter是用于设置的。下面的代码很糟糕,因为您实际上创建了一个方法,它不会执行它所说的操作
public Rectangle setWidth(int width) {
return new Rectangle(width, this.height);
}
问题在于合同,即使用矩形
的程序员的期望
合同规定,您可以执行一个setWidth(15)
,然后getWidth()
将返回15
,直到您使用不同的值执行另一个setWidth
。从
setHeight
的角度来看,这意味着它不能更改height
。继续这种思路,setter的约定是“将此属性更新为参数值,并保持所有其他属性不变”
现在在Square
中,新的不变量getWidth()==getHeight()
强制setWidth
也设置高度,瞧:违反了setHeight
的约定
当然,您可以在Rectangle.setWidth()
的契约(即方法文档)中明确声明,如果调用setHeight()
,宽度可能会改变。但是现在你的