Liskov 替换原则 VS 接口隔离原则

Liskov substitution principle VS Interface Segregation principle

我在理解这两个原则时遇到了一些麻烦。这个有点long-read-question,耐心点

让我们假设我们有一个 class

abstract class Shape {
    abstract void onDraw();
}

和界面

interface SideCountable {
  int getSidesCount();
}

然后我们创建两个children classes

class Quad extends Shape {

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

class Triangle extends Shape {

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

现在我们将制作 Shape 工具 SideCountable

abstract class Shape implements SideCountable{
    abstract void onDraw();
}

并在 children classes

中进行更改
class Quad extends Shape {

   @Override
   public int getSidesCount() {
       return 4;
   }

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

class Triangle extends Shape {

   public int getSidesCount() {
       return 3;
   }

   public void onDraw() {
   //some draw logic here
   }
}

并为此结构创建测试函数(遵循 LSP):

public void printSidesCount(List<Shape> shapes) {
    for(int i=0; i < shapes.size(); i++) {
        System.out.println(shapes.get(i).getSidesCount());
    }
}

我想在这里停下来,因为实际上在下一步我被卡住了。 如果我们创建第三个 class Circle 会怎么样?

class Circle extends Shape {

   public int getSidesCount() {
       return ???;
   }

   @Override
   public void onDraw() {
   //some draw logic here
   }
}

圆没有边,所以 SideCountable 这个 children 的实现听起来很荒谬。好的,我们可以只将实现转移到 Quad 和 Triangle,但在这种情况下,LSP 将不再起作用。有人可以描述我应该做的最好的方法吗?

首先,你的方法printSidesCount只需要知道列表包含SideCountable个对象。所以给它的参数类型 List<Shape> 比必要的更具体。给它一个 List<SideCountable> 代替:

public void printSidesCount(List<SideCountable> sideCountables) {
    for(int i=0; i < (); i++) {
        System.out.println(sideCountables.get(i).getSidesCount());
    }
}

甚至 List<? extends SideCountable> 这意味着 "a list of some arbitrary unknown type that implements SideCountable":

public void printSidesCount(List<? extends SideCountable> sideCountables) {
    for(int i=0; i < sideCountables.size(); i++) {
        System.out.println(sideCountables.get(i).getSidesCount());
    }
}

如果不是所有的形状都有可数边,那么class Shape 不应该实现接口SideCountable。相反,除了扩展 class Shape:

之外,让 classes QuadTriangle 实现接口 SideCountable
class Quad extends Shape implements SideCountable {
    // ...
}

class Triangle extends Shape implements SideCountable {
    // ...
}

并使 class Circle 扩展 Shape 但不实施 SideCountable:

class Circle extends Shape { // Not SideCountable
    // ...
}

当你这样做时,类型系统会帮助你:

  • 您可以将 List<Quad>List<Triangle> 传递给 printSidesCount,因为这些类型实现了 SideCountable
  • 您不能将 List<Circle> 传递给 printSidesCount,这很好,因为尝试这样做对于圈子列表没有意义