假设我有一个抽象类,如:
public abstract class Pet {
private final String name;
public Pet(String name) {
this.name = name
};
public abstract boolean getsSpecialTreatment();
}
public final class Dog extends Pet {
@Override public boolean getsSpecialTreatment() { return true; }
}
public final class Cat extends Pet {
@Override public boolean getsSpecialTreatment() { return false; }
}
Run Code Online (Sandbox Code Playgroud)
我的程序将Pet根据是否设置特殊处理标志来区别对待s.我的问题是,这是否违反Liskov替代原则,该原则指出:
[...]在计算机程序中如果S是T的子类型,那么T类型的对象可以用S类型的对象替换而不改变该程序的任何所需属性(正确性,执行的任务)等).
我正在使用违反Liskov替换原则的API:它抛出自己的Exception类型,扩展Exception,但将来自基类的异常消息放入新的ErrorCode字段中,并将自己的(无用的)消息放入Message字段中.因此,要显示正确的消息,我需要将Exception转换为DerivedException类型并使用ErrorCode字段.如果我将它视为异常对象,我会得到错误的消息.
现在这让我感觉很震撼,但是它很容易解决:我可以捕获DerivedException并将其用作程序员的预期.所以我的问题是:利斯科夫原则有什么大不了的?使用违反原则的层次结构,人们可能会遇到哪些实际问题?
我在C#中编写了一系列集合类,每个集合类都实现了类似的自定义接口.是否可以为接口编写单个单元测试集合,并在几个不同的实现上自动运行它们?我想避免每个实现的任何重复的测试代码.
我愿意研究任何框架(NUnit等)或Visual Studio扩展来实现这一目标.
对于那些希望这样做的人,我根据avandeursen公认的解决方案发布了我的具体解决方案作为答案.
.net unit-testing liskov-substitution-principle visual-studio-2010
我有一些处理程序("控制器")类,他们可以以某种方式处理项目:
interface IHandler
{
public function execute(Item $item);
}
class FirstHandler implements IHandler
{
public function execute(Item $item) { echo $item->getTitle(); }
}
class SecondHandler implements IHandler
{
public function execute(Item $item) { echo $item->getId() . $item->getTitle(); }
}
class Item
{
public function getId() { return rand(); }
public function getTitle() { return 'title at ' . time(); }
}
Run Code Online (Sandbox Code Playgroud)
但是我需要在子Item类中添加一些新功能:
class NewItem extends Item
{
public function getAuthor() { return 'author ' . rand(); }
}
Run Code Online (Sandbox Code Playgroud)
并在SecondHandler中使用它
class …Run Code Online (Sandbox Code Playgroud) php architecture oop inheritance liskov-substitution-principle
此LSP违规会引发致命错误:
abstract class AbstractService { }
abstract class AbstractFactory { abstract function make(AbstractService $s); }
class ConcreteService extends AbstractService { }
class ConcreteFactory extends AbstractFactory { function make(ConcreteService $s) {} }
Run Code Online (Sandbox Code Playgroud)
此LSP违规也会引发致命错误:
interface AbstractService { }
interface AbstractFactory { function make(AbstractService $s); }
class ConcreteService implements AbstractService { }
class ConcreteFactory implements AbstractFactory { function make(ConcreteService $s) {} }
Run Code Online (Sandbox Code Playgroud)
虽然这种LSP违规只会引发警告:
class Service { }
class Factory { function make(Service $s) {} }
class MyService …Run Code Online (Sandbox Code Playgroud) Liskov替代原理对派生类中的方法签名施加的规则之一是:
子类型中方法参数的矛盾性。
如果我理解正确的话,就是说派生类的重写函数应允许使用自变量(超类型自变量)。但是,我不明白这条规则背后的原因。由于LSP主要讨论动态地将类型与那里的子类型(而不是超类型)进行绑定以实现抽象,因此让超类型作为派生类中的方法参数对我来说是很困惑的。我的问题是:
我正在阅读面向对象编程语言的类的一些幻灯片,并进入类型子类型定义:
Barbara Liskov,"数据抽象和层次结构",SIGPLAN Notices,23,5,1988年5月:
这里需要的是类似下面的替换属性:如果对于类型S的每个对象o_s,存在类型为T的对象o_T,使得对于以T表示的所有程序P
,当o_S被替换时,P的行为不变.对于o_T,则S是T的子类型
然后它举个例子:
Point = {x:Integer,y:Integer}
PositivePoint = {x:Positive,y:Positive}
其中Positive = {k:Integer | k> 0}我们可以说PositivePoint≤Point吗?
是的,因为PositivePoint类型的元素可能总是在Point术语中定义的程序中替换Point类型的元素!
现在......对我而言,它似乎应该完全相反:Point≤DealgePoint因为我无法在使用带负坐标的Point的程序中使用PositivePoint,而我可以反过来.
我怀疑,如果语法是Type ? Sub-type或Sub-Type ? Type,但声明似乎更清楚,什么是错的呢?
只是为了让事情变得更容易,问题是:你能说这PositivePoint是一个子类型Point吗?为什么?
我在这里报告我在评论中写的内容,希望它能让我的问题更加清晰:
假设程序必须绘制从
Point(-100,-100)到Point(100,100 )的方形图.如果你使用类型会发生什么PositivePoint?该计划的行为会保持不变吗?它不会.这种"不变的行为"是我唯一没有得到的.如果子类型的定义只是inheriting and overriding来自其他类型,那就没关系,但似乎并非如此.
我已经阅读了很多这方面的文章,但我还有两个问题.
问题#1 - 关于依赖性倒置:
它声明高级类不应该依赖于低级类.两者都应该取决于抽象.抽象不应该依赖于细节.细节应取决于抽象.
例如 :
public class BirthdayCalculator
{
private readonly List<Birthday> _birthdays;
public BirthdayCalculator()
{
_birthdays = new List<Birthday>();// <----- here is a dependency
}
...
Run Code Online (Sandbox Code Playgroud)
修复:把它放在一个ctor.
public class BirthdayCalculator
{
private readonly IList<Birthday> _birthdays;
public BirthdayCalculator(IList<Birthday> birthdays)
{
_birthdays = birthdays;
}
Run Code Online (Sandbox Code Playgroud)
如果它将在ctor中 - 我将不得不每次使用该课程时发送它.所以在打电话给BirthdayCalculator班级时我必须保留它.它可以这样做吗?
我可以争辩说,在修复之后,仍然 - IList<Birthday> _birthdays不应该在那里(Birthdayin IList) - 但它应该是IList<IBirthday>.我对吗 ?
问题#2 - 关于利斯科夫换人:
派生类必须可替代其基类
或更准确:
设q(x)是关于类型T的对象x可证明的属性.对于类型S的对象y,q(y)应该为真,其中S是T的子类型.
(已经读过这个)
例如:
public …Run Code Online (Sandbox Code Playgroud) c# design-patterns liskov-substitution-principle inversion-of-control solid-principles
Liskov 替换原则指出
如果
S是 的子类型T,则类型的对象T可以替换为类型的对象,S而不会改变该程序的任何所需属性。
但是,在 Scala 中,PartialFunction并非在所有情况下都适用/定义。
trait Function1 {
def apply(x: A): R
}
trait PartialFunction extends Function1 {
def apply(x: A): R
def isDefinedAt(x: A): Boolean
}
Run Code Online (Sandbox Code Playgroud)
如果将 aPartialFunction应用于未定义的值,您将收到异常。
PartialFunction在 Scala 中创建一个的方便方法是使用模式匹配。这样做,您会收到一个MatchError未定义的值。
val fn:Function1[String, Int] = s => s.length
val pf:PartialFunction[String, Int] = {
case "one" => 3
}
def program(f:Function1[String, Int], s:String):(Boolean, Int) = (
f.isInstanceOf[Function1[String, Int]], f(s)
)
program(fn, "one") == …Run Code Online (Sandbox Code Playgroud) 我遇到了里氏替换原则的问题,并且不太确定解决它的最佳方法是什么。
有问题的代码
class BaseModel:
def run(self, base_model_input: BaseModelInput) -> BaseModelOutput:
"""Throws NotImplemented or @abstractmethod"""
pass
class SpecificModel(BaseModel):
def run(self, specific_input: SpecificModelInput) -> SpecificModelOutput:
# do things...
Run Code Online (Sandbox Code Playgroud)
我很清楚为什么这不是一个很好的代码,以及为什么它违反了里氏替换原则。我想知道如何更好地设计我的系统以避免这个问题。
从根本上讲,我有一个BaseModel类似于接口的类,提供一些run扩展类必须实现的方法。但是扩展类还处理特定的输入/输出,它们也是基本输入/输出类的扩展(继承SpecificModelInput并BaseModelInput添加一些字段和功能,与输出相同)
这里更好的方法是什么?
python design-patterns liskov-substitution-principle python-3.x python-typing
liskov-substitution-principle ×10
oop ×3
c# ×2
inheritance ×2
php ×2
.net ×1
architecture ×1
java ×1
python ×1
python-3.x ×1
scala ×1
unit-testing ×1