Oop Liskov替代原理的一个例子是什么?
我听说Liskov替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用示例是什么?在一个电路板阵列中实现ThreeDBoard会有那么大的用处吗 也许你想把不同平面上的三块木板的切片当作一块木板。在这种情况下,您可能需要为Board抽象出一个接口(或抽象类),以允许多个实现Oop Liskov替代原理的一个例子是什么?,oop,definition,solid-principles,design-principles,liskov-substitution-principle,lsp,Oop,Definition,Solid Principles,Design Principles,Liskov Substitution Principle,Lsp,我听说Liskov替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用示例是什么?在一个电路板阵列中实现ThreeDBoard会有那么大的用处吗 也许你想把不同平面上的三块木板的切片当作一块木板。在这种情况下,您可能需要为Board抽象出一个接口(或抽象类),以允许多个实现 就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard(尽管上述方法都不适用)确定一个板接口。Liskov替换原则(LSP)是面向对象编程中的一个概念,它指出: 使用指针或 对基类的引用必须是
就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard(尽管上述方法都不适用)确定一个板接口。Liskov替换原则(LSP)是面向对象编程中的一个概念,它指出: 使用指针或 对基类的引用必须是 能够使用派生类的对象 不知道 LSP的核心是接口和契约,以及如何决定何时扩展一个类,而不是使用另一种策略,如组合来实现您的目标 我所见过的最有效的说明这一点的方法是在。它们提供了一个场景,您是一个项目的开发人员,为战略游戏构建一个框架 它们提供了一个类,该类表示如下所示的板: 所有这些方法都以X和Y坐标为参数,在二维
瓷砖数组中定位瓷砖位置。这将允许游戏开发者在游戏过程中管理棋盘上的单元
这本书接着改变了要求,说游戏框架还必须支持3D游戏板,以适应有飞行的游戏。因此,引入了一个扩展Board
的ThreeDBoard
类
乍一看,这似乎是个好决定Board
提供了高度
和宽度
属性,ThreeDBoard
提供了Z轴
当您查看从董事会继承的所有其他成员时,它就会崩溃。AddUnit
、GetTile
、GetUnits
等方法都在Board
类中同时使用X和Y参数,但ThreeDBoard
也需要Z参数
因此,必须使用Z参数再次实现这些方法。Z参数与Board
类没有上下文,从Board
类继承的方法失去了意义。试图使用ThreeDBoard
类作为其基类Board
的代码单元将非常不走运
也许我们应该找到另一种方法。ThreeDBoard
应该由Board
对象组成,而不是扩展Board
。每单位Z轴上有一块板
对象
这允许我们使用良好的面向对象原则,如封装和重用,并且不违反LSP
使用指向基类的指针或引用的函数必须能够在不知道的情况下使用派生类的对象
当我第一次读到LSP时,我认为这是一个非常严格的含义,本质上等同于接口实现和类型安全转换。这意味着LSP要么是由语言本身来保证,要么不是由语言本身来保证。例如,在这个严格意义上,就编译器而言,ThreeDBoard当然可以替代Board
在阅读了更多关于LSP的概念之后,我发现LSP的解释通常比这更广泛
简言之,客户端代码“知道”指针后面的对象是派生类型而不是指针类型的意思并不限于类型安全。遵守LSP也可以通过探测对象的实际行为来测试。也就是说,检查对象的状态和方法参数对方法调用结果的影响,或者检查对象抛出的异常类型
再回到这个例子,理论上,Board方法可以在ThreeDBoard上正常工作。然而,在实践中,如果不妨碍ThreeDBoard打算添加的功能,就很难防止客户端可能无法正确处理的行为差异
有了这些知识,评估LSP遵从性可以成为一个很好的工具,用于确定组合何时是扩展现有功能而不是继承的更合适的机制。Robert Martin有一个很好的方法。它讨论了可能违反该原则的微妙和不太微妙的方式
本文的一些相关部分(请注意,第二个示例非常精简):
违反LSP的一个简单示例
这个原理最明显的违反之一就是使用C++。
运行时类型信息(RTTI),用于根据
对象的类型。i、 e:
void DrawShape(const Shape& s)
{
if (typeid(s) == typeid(Square))
DrawSquare(static_cast<Square&>(s));
else if (typeid(s) == typeid(Circle))
DrawCircle(static_cast<Circle&>(s));
}
[…]想象有一天,用户需要操作的能力
除了矩形,还有正方形。[……]
显然,对于所有正常的意图和目的来说,正方形都是矩形。
由于ISA关系成立,因此对Square
类作为从矩形
派生的对象。[……]
Square
将继承SetWidth
和SetHeight
函数。这些
函数完全不适合正方形
,因为
正方形的高度是相同的。这应该是一条重要的线索
设计有问题。然而,有一种方法可以
回避这个问题。我们可以覆盖SetWidth
和SetHeight
[…]
但请考虑以下功能:
void f(Rectangle& r)
{
r.SetWidth(32); // calls Rectangle::SetWidth
}
public void A(Rectangle r)
{
r.SetWidth(32); // calls Rectangle.SetWidth
}
如果我们将对Square
对象的引用传递到此函数中,则
Square
o
class Rectangle {
int getHeight()
void setHeight(int value) {
postcondition: width didn’t change
}
int getWidth()
void setWidth(int value) {
postcondition: height didn’t change
}
}
class Square extends Rectangle { }
void invariant(Rectangle r) {
r.setHeight(200)
r.setWidth(100)
assert(r.getHeight() == 200 and r.getWidth() == 100)
}
class Base:
def Foo(self, arg):
# *... do stuff*
class Derived(Base):
def Foo(self, arg):
# *... do stuff*
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);
}
}
public class Bird{
public void fly(){}
}
public class Duck extends Bird{}
public class Ostrich extends Bird{}
public class Bird{}
public class FlyingBirds extends Bird{
public void fly(){}
}
public class Duck extends FlyingBirds{}
public class Ostrich extends Bird{}
r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);
class Square extends Rectangle {
setDimensions(width, height){
assert(width == height);
super.setDimensions(width, height);
}
}
r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);
// 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.
}
}
interface Account
{
/**
* Withdraw $money amount from this account.
*
* @param Money $money
* @return mixed
*/
public function withdraw(Money $money);
}
class DefaultAccount implements Account
{
private $balance;
public function withdraw(Money $money)
{
if (!$this->enoughMoney($money)) {
return;
}
$this->balance->subtract($money);
}
}
interface Account
{
/**
* Withdraw $money amount from this account if its balance is enough.
* Otherwise do nothing.
*
* @param Money $money
* @return mixed
*/
public function withdraw(Money $money);
}
class Client
{
public function go(Account $account, Money $money)
{
if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {
return;
}
$account->withdraw($money);
}
}
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
class Car extends TransportationDevice
{
@Override
void startEngine() { ... }
}
class Bicycle extends TransportationDevice
{
@Override
void startEngine() /*problem!*/
}
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
}
class DevicesWithoutEngines extends TransportationDevice
{
void startMoving() { ... }
}
class DevicesWithEngines extends TransportationDevice
{
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
class Car extends DevicesWithEngines
{
@Override
void startEngine() { ... }
}
class Bicycle extends DevicesWithoutEngines
{
@Override
void startMoving() { ... }
}
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();
}
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();
}
public class Rectangle
{
private double width;
private double height;
public double Width
{
get
{
return width;
}
set
{
width = value;
}
}
public double Height
{
get
{
return height;
}
set
{
height = value;
}
}
}
public class Square : Rectangle
{
}
public class Square : Rectangle
{
public double SetWidth
{
set
{
base.Width = value;
base.Height = value;
}
}
public double SetHeight
{
set
{
base.Height = value;
base.Width = value;
}
}
}
Square s = new Square();
s.SetWidth(1); // Sets width and height to 1.
s.SetHeight(2); // sets width and height to 2.
public void A(Rectangle r)
{
r.SetWidth(32); // calls Rectangle.SetWidth
}