Liskov替换原则(LSP)

Liskov替换原则(The Liskov Substitution Principle)

子类型(subtype)必须能够替换掉它们的基类型(base type)。

一个违反LSP的简单例子

public class AntiLspDemo {

	public void drawShape(final Shape shape) {
		if (shape.getItsType() == ShapeType.Square) {
			final Square square = (Square) shape;
			square.draw();
		} else if (shape.getItsType() == ShapeType.Circel) {
			final Circle circle = (Circle) shape;
			circle.draw();
		}
	}
}

enum ShapeType {
	/**
	 * 正方形.
	 */
	Square,
	/**
	 * 圆形.
	 */
	Circel
}

class Point {
	private double x;
	private double y;
	// get/set...
}

class Shape {
	private ShapeType itsType;

	/**
	 * 获取itsType.
	 * @return the itsType
	 */
	public ShapeType getItsType() {
		return itsType;
	}

	/**
	 * 设置itsType.
	 * @param newItsType the itsType to set
	 */
	public void setItsType(ShapeType newItsType) {
		itsType = newItsType;
	}
	
}

class Circle extends Shape {
	private Point itsCenter;
	private double itsRadius;
	
	public Circle() {
		this.setItsType(ShapeType.Circel);
	}
	
	public void draw() {
		System.out.println("绘制Circle...");
	}
}

class Square extends Shape {
	private Point itsTopLeft;
	private double itsSide;
	
	public Square() {
		this.setItsType(ShapeType.Square);
	}
	
	public void draw() {
		System.out.println("绘制Square...");
	}
}

正方形和矩形,更微妙的违规

        我们经常说继承是IS-A(“是一个”)关系。如果一个新类型的对象被认为和一个已有类的对象之间满足IS-A关系,那么这个新对象的类应该从这个已用对象的类派生。

        一个正方形是一个矩形,所以Square类就应该派生在Rectangle类。不过,这将带来一些微妙但极为值得重视的问题。

public class Rectangle {

	/**
	 * 左上角坐标.
	 */
	private Point topLeft;
	/**
	 * 宽度.
	 */
	private double width;
	/**
	 * 高度.
	 */
	private double height;
	/**
	 * 获取topLeft.
	 * @return the topLeft
	 */
	public Point getTopLeft() {
		return topLeft;
	}
	/**
	 * 设置topLeft.
	 * @param newTopLeft the topLeft to set
	 */
	public void setTopLeft(Point newTopLeft) {
		topLeft = newTopLeft;
	}
	/**
	 * 获取width.
	 * @return the width
	 */
	public double getWidth() {
		return width;
	}
	/**
	 * 设置width.
	 * @param newWidth the width to set
	 */
	public void setWidth(double newWidth) {
		width = newWidth;
	}
	/**
	 * 获取height.
	 * @return the height
	 */
	public double getHeight() {
		return height;
	}
	/**
	 * 设置height.
	 * @param newHeight the height to set
	 */
	public void setHeight(double newHeight) {
		height = newHeight;
	}
	
}

public class Square extends Rectangle {

	public void setWidth(double width) {
		super.setWidth(width);
		super.setHeight(width);
	}
	
	public void setHeight(double height) {
		super.setWidth(height);
		super.setHeight(height);
	}
}

真正的问题

        Square类没有违反正方形的不变性,但是Square派生自Rectangle,Square类违反了Rectangle类的不变性。

public void g(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    assert(r.area() == 20)
}

有效性并非本质属性

        一个模型,如果孤立地看,并不具有真正意义上的有效性。模型的有效性只能通过它的Client程序来体现。这又是一个实践TDD的好理由。

IS-A是关于行为的

        对于那些不是g的调用者而言,正方形可以是长方形,但是从g的角度,Square对象绝对不是Rectangle对象。Square对象的行为方式和函数g所期望的Rectangle对象的行为方式不相容。从行为方式的角度来看,Square不是Rectangle,对象的行为方式才是软件真正所关注的。

基于契约设计

        基于契约设计(Design By Contract),类的编写者显示地规定针对该类的契约。契约是通过为每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的。要执行一个方法,前置条件必须为真。执行完毕后,保证后置条件为真。 

Rectangle.setWidth(double w)的后置条件:
assert((width == w) && (height == old.height));

        派生类的前置条件和后置条件规则是:

在重新声明派生类中的例程(routine)时,

只能使用相等或更弱的前置条件,只能使用相等或更强的后置条件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值