Liskov替换原则规定子类型应该可替代该类型(不改变程序的正确性).
我已经读过关于方形/矩形的例子,但我认为车辆的一个例子可以让我更好地理解这个概念.
Liskov替换原则要求子类型必须满足超类型的契约.根据我的理解,这将导致ReadOnlyCollection<T>违反Liskov. ICollection<T>合同公开Add和Remove运营,但只读子类型不符合本合同.例如,
IList<object> collection = new List<object>();
collection = new System.Collections.ObjectModel.ReadOnlyCollection<object>(collection);
collection.Add(new object());
-- not supported exception
Run Code Online (Sandbox Code Playgroud)
显然需要不可变的集合.有没有关于.NET的建模方法的事情?有什么更好的方法呢? IEnumerable<T>在一个集合中做得很好,至少看起来是不可改变的.但是,语义非常不同,主要是因为IEnumerable没有明确地暴露任何状态.
在我的特定情况下,我正在尝试构建一个不可变的DAG类来支持FSM.我显然在开始时需要AddNode/ AddEdge方法,但我不希望它一旦运行就可以更改状态机.我很难表示DAG的不可变和可变表示之间的相似性.
现在,我的设计涉及预先使用DAG Builder,然后创建一次不可变图,此时它不再可编辑.Builder和具体的不可变DAG之间唯一的通用接口是Accept(IVisitor visitor).我担心,面对可能更简单的选择,这可能是过度设计/过于抽象.与此同时,我无法接受我可以在我的图形界面上公开可能NotSupportedException在客户端获得特定实现时抛出的方法.处理这个问题的正确方法是什么?
c# liskov-substitution-principle immutability readonly-collection directed-acyclic-graphs
查看ReadOnlyCollection类的规范,它确实实现了IList接口.
IList接口具有Add/Update/Read方法,我们称之为接口的前置条件.在我的应用程序的任何地方,如果我有一个IList我应该能够做所有这些操作.
但是,如果我在代码中的某处返回ReadOnlyCollection并尝试调用.Add(...)方法呢?它会抛出NotSupportedException.你认为这是一个糟糕设计的好例子吗?另外,这个类是否打破了Liskov替换原则?
为什么Microsoft以这种方式实现?是否更容易(和更好)使这个ReadOnlyCollection只实现IEnumerable接口(顺便说一下,已经只读)?
我的情况与Steve Complete所提到的Steve McConnell非常相似.只有我的问题是基于车辆和三轮车才恰好依据法律属于汽车类别.到目前为止,汽车有四个轮子.无论如何,我的域名都不必要地复杂,所以很容易坚持使用下面的猫.
怀疑覆盖例程的类并在派生例程中不执行任何操作这通常表示基类设计中的错误.例如,假设您有一个类Cat和一个例程Scratch(),并假设您最终发现某些猫被声明并且无法划伤.您可能想要创建一个派生自名为ScratchlessCat的Cat的类,并覆盖Scratch()例程以不执行任何操作.这种方法存在几个问题:
它通过改变其接口的语义来违反Cat类中提供的抽象(接口契约).
当您将其扩展到其他派生类时,此方法很快就会失控.当你找到一只没有尾巴的猫时会发生什么?还是一只没有抓到老鼠的猫?还是一只不喝牛奶的猫?最终你会得到像ScratchlessTaillessMicelessMilklessCat这样的派生类.
随着时间的推移,这种方法会导致代码难以维护,因为祖先类的接口和行为对其后代的行为意味着很少或根本没有.
解决此问题的地方不在基类中,而是在原始Cat类中.创建一个Claws类并在Cats类中包含它.根本问题是假设所有猫都抓了,所以在源头修复这个问题,而不是仅仅在目的地包扎它.
根据他上面的伟大着作中的文字.以下是坏事
父类不必是抽象的
public abstract class Cat {
public void scratch() {
System.out.println("I can scratch");
}
}
Run Code Online (Sandbox Code Playgroud)
派生类
public class ScratchlessCat extends Cat {
@Override
public void scratch() {
// do nothing
}
}
Run Code Online (Sandbox Code Playgroud)
现在他建议创建另一个类Claws,但我不明白如何使用这个类来避免需要ScratchlessCat#Scratch.
我更多地是出于好奇而不是真正关心它,但我一直想知道JavaScript事件系统是否违反了Liskov替换原则(LSP).
通过调用EventTarget.dispatchEvent,我们可以调度一个Event可能由注册处理的任意类型EventListener.
interface EventListener {
void handleEvent(in Event evt);
}
Run Code Online (Sandbox Code Playgroud)
如果我正确理解LSP,那就意味着不anyEventListener.handleEvent(anyEvent)应该失败.但是,通常情况并非如此,因为事件侦听器通常会使用专用Event子类型的属性.
在不支持泛型的类型语言中,该设计基本上需要将Event对象向下转换为期望的子类型EventListener.
根据我的理解,上述设计可能被视为违反LSP.我是否正确或type通过注册监听器提供的简单事实EventTarget.addEventListener可以防止LSP违规?
编辑:
虽然每个人似乎都在关注Event子类没有违反LSP的事实,但实际上我关注的是EventListener实现者会通过加强EventListener接口的前提条件来违反LSP .void handleEvent(in Event evt)合同中没有任何内容告诉您通过传递错误的Event子类型可能会破坏某些内容.
在具有泛型的强类型语言中,接口可以表达为EventListener<T extends Event>使得实现者可以使合同明确,例如SomeHandler implements EventListener<SomeEvent>.
在JS中,显然没有实际的接口,但事件处理程序仍然需要符合规范,并且该规范中没有任何内容允许处理程序判断它是否可以处理特定类型的事件.
这不是一个真正的问题,因为不希望监听器自己被调用,而是EventTarget被注册并与特定类型相关联的监听器调用.
我只是对根据理论是否违反LSP感兴趣.我想知道是否要避免违规(如果理论上认为是这样),合同将需要像以下那样(尽管它在实用主义方面可能做得更糟糕):
interface EventListener {
bool handleEvent(in Event evt); //returns wheter or not the event …Run Code Online (Sandbox Code Playgroud) 我来自 Java 背景,我正在尝试围绕 Haskell 的类型系统进行研究。在 Java 世界中,Liskov 替换原则是基本规则之一,我试图了解是否(如果是,如何)这也是适用于 Haskell 的概念(请原谅我对 Haskell 的有限理解,我希望这个问题甚至是有道理的)。
例如,在 Java 中,公共基类Object定义了boolean equals(Object obj)所有 Java 类都继承的方法,并允许进行如下比较:
String hello = "Hello";
String world = "World";
Integer three = 3;
Boolean a = hello.equals(world);
Boolean b = world.equals("World");
Boolean c = three.equals(5);
Run Code Online (Sandbox Code Playgroud)
不幸的是,由于 Liskov 替换原则,Java 中的子类在它接受的方法参数方面不能比基类更具限制性,因此 Java 也允许一些永远不会为真的无意义比较(并且可能导致非常微妙的错误) :
Boolean d = "Hello".equals(5);
Boolean e = three.equals(hello);
Run Code Online (Sandbox Code Playgroud)
另一个不幸的副作用是,正如 Josh Bloch很久以前在Effective Java 中指出的那样,equals在存在子类型的情况下,基本上不可能按照其约定正确实现方法(如果在子类中引入额外的字段,实施将违反合同的对称性和/或传递性要求)。
现在,Haskell 的Eq 类型类是一个完全不同的动物:
Prelude> data Person = …Run Code Online (Sandbox Code Playgroud) Liskov 替换原则 (LSP) 和接口隔离原则 (ISP) 之间有什么核心区别吗?最终,两者都保证设计具有通用功能的界面,并在您具有特殊功能时引入新界面。
liskov-substitution-principle interface solid-principles interface-segregation-principle
松散地说,Liskov Substitution Principle声明派生类可以替代基类而不影响用户.在基类是抽象类的情况下,这意味着没有用户使用基类的实例,Liskov继承限制是否仍然适用于派生类?
例如,我们有以下结构:
class Base
{
[pure]
public virtual bool IsValid(/*you can add some parameters here*/)
{
//body
}
}
class Child : Base
{
public override bool IsValid(/*you can add some parameters here*/)
{
//body
}
}
Run Code Online (Sandbox Code Playgroud)
你能否填写Base::IsValid()并Child::IsValid()与不同的机构,但没有与LSP的冲突?让我们想象它只是分析的方法,我们无法改变实例的状态.我们能做到吗?我对任何一个例子感兴趣.在一般情况下,我试图了解虚拟(身体)布尔方法是否是反模式.
oop design-patterns liskov-substitution-principle solid-principles
这个问题已被问过并回答过很多次,但似乎没有一个答案对我有用。
我一直在尝试clangd在 nvim lsp 中进行设置。我使用bear来生成compile_commands.json,但 clangd 仍然给我错误,告诉我它找不到标准库头。这是一个最小的例子:
主要.cpp:
#include <iostream>
using namespace std;
int main(){
cout << "hello clangd";
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后我运行:bear -- g++ main.cpp,它编译并创建一个compile_commands.json包含以下内容的文件:
[
{
"arguments": [
"/usr/bin/g++",
"-c",
"main.cpp"
],
"directory": "/home/xxx/tmp/hello_clangd",
"file": "/home/xxx/tmp/hello_clangd/main.cpp"
}
]
Run Code Online (Sandbox Code Playgroud)
我还尝试使用 cmake 标志进行编译来生成compile_commands.json,但我遇到了同样的问题。我可以获取该文件,但语言服务器仍然无法正常工作。
我已经能够将 clang 与vim-pio 一起使用,所以看起来它没有损坏。我缺少什么。
编辑:顺便说一句,我在 ubuntu 上
liskov-substitution-principle ×10
oop ×4
c# ×2
java ×2
c++ ×1
clangd ×1
collections ×1
frameworks ×1
g++ ×1
haskell ×1
immutability ×1
interface ×1
interface-segregation-principle ×1
javascript ×1
neovim ×1
subtyping ×1
types ×1