我们说我有一个抽象类鸟,其中一个函数是fly(int height)。
我有许多不同的鸟类,每个鸟类都有自己不同的苍蝇实现,并且该功能在整个应用程序中得到广泛使用。
有一天,我的老板来了,并要求我添加一只鸭子,它可以做其他鸟类所做的一切,除了它不会飞,而是在应用池塘里游泳。
将鸭子添加为鸟类的子类型会违反Liskov替换规则,因为在调用duck.fly时,我们会抛出异常,不执行任何操作或违反正确性原则。
在牢记SOLID设计原则的同时,您将如何介绍此更改?
答案 0 :(得分:2)
您确定的问题是由于您的模型具有以下不兼容的功能。
您需要更改其中一个以使语句保持一致。要改变哪一个将是最适合您使用的设计问题,以及进行更改所涉及的问题。
你可以通过引入类来区分飞行和不会飞的鸟类来解决这个问题。也许为了让变化更小,你的鸟类继续飞行,所以鸭子不是鸟,而是一只不会飞的鸟。或者你可以扩展鸟类来创建飞鸟类并将飞行方法移动到那里 - 两者都会涉及对现有代码的更改。
抛出异常是第3点的一种欺骗 - 你的鸭子仍然没有真正飞行,它只是使问题成为运行时间而不是设计时间的问题。它可能很快,但它不是一种非常安全的方法,它需要调用代码,避免在恰好是鸭子的鸟类上调用fly。
是否允许苍蝇不为鸭子做任何事情取决于你的调用代码期望飞行一般做什么 - 实际上你通过将第1点中的fly的含义改为“所有鸟类都可以被要求修复不一致性”飞(虽然有些人不会)“。在这种情况下,飞行真的变成了飞行,可能是不完美设计的标志,但可能是实用的解决方案 - 特别是在适应现有设计时。
你建议不做任何选项的事实可能表明一条最不发生剧变的路线,因为如果你让苍蝇不为鸭子做什么,那么你仍然需要一些鸭子特定的代码让它游泳,所以你可以接受在现实世界中鸭子可以飞,并且鸭子特定的代码叫游泳而不是飞行 - 不像飞行。
更一般地说,我认为你实际描述的是第1点的变化,从“所有鸟类可以飞行”到“所有鸟类都可以移动”,然后非鸭子实施移动,因为苍蝇和鸭子实施移动游泳(无论是他们也有飞行方法或不)。这可能涉及将一些现有的呼叫改为飞入移动呼叫。
答案 1 :(得分:1)
我看到3个选项:
使用Bird
作为常用功能的抽象基类,并从中FlyingBird
和AquaticBird
派生。
使用Zoran Horvat所描述的对象组合和访客模式:https://vimeo.com/195774910(在任何情况下都值得关注) - 尽管如此,在手头的案例中似乎有些过分。
按照您的描述使用解决方案。
最后,关键在于平衡。 如果您期望有许多不同能力的不同鸟类加入,那么您应该认真考虑选项2,否则取决于您如何使用这些类别,请选择1到3之间。
根据Gilads关于选项2(对象构成和访客模式)的评论进行更新
这里最重要的是你可以拥有一个课程Bird
并附上能力,例如Flying
和Swimming
,之后你可能会决定你有其他能力或某种分类。你可以通过创建不同的接口(IFlyable
,ISwimmable
)来做到这一点:
public class SomeFlyingBird : BirdBase, IFlyable
{
....
}
public class Duck : BirdBase, ISwimmable
{
....
}
然后使用访问者模式访问特定操作。
或者,如果你寻找更强大和优雅的东西,你可以为每个能力创建不同的类并将它们附加到Bird类:
public class Bird/Animal
{
string name;
List<Ability> abilities = new List<Ability>();
public Bird (string name)
{
this.name = name;
}
public void Add (Ability ability)
{
this.abilities.Add(ability);
}
}
稍后您可以使用一些静态类来创建鸟类:
public static Bird CreateDuck ()
{
var duck = new Bird("Donald");
duck.Add(new SwimAbility());
return duck;
}
能力和游泳可以是这样的:
public abstract class Ability
{
public virtual void Accept(AbilityVisitor visitor) =>
visitor.Visit(this);
}
public class SwimAbility : Ability
{
public override void Accept(AbilityVisitor visitor)
{
base.Accept(visitor);
visitor.Visit(this);
}
}
我没有附上完整的代码,因为它不是我的,这个答案太长了。我再次说,对于你所描述的案例,这看起来有点过分了。
我强烈建议您观看Zoran Horvat的视频并下载并播放他提供的代码: http://www.postsharp.net/blog/post/webinar-recording-object-composition
答案 2 :(得分:0)
所以你的问题是
(1)鸭子是一只鸟。
(2)但它无法飞行。
所以真正的问题是,
<强>解决方案:强>
因此,这里采用的最简单,最明智的解决方案是 使用通用名称 作为方法名称,以便双方都能达成一致。所以在这里 你可以将方法Bird.fly()重命名为Bird.move() 。
所以Duck.move()和Crow.move()都有意义,但他们以自己的方式做。
所有其他可用的解决方案都会杀死多态性(使用两个不同的基类)。这意味着您无法通过一种方法同时调用Duck和Crow(分别游泳和飞行)。 (比如Bird.move()做)。
当你的应用程序构建时,它会花费你很多,因为你必须通过显式或隐式类型转换来区分它们,这是非常糟糕的。它会浪费你的时间,使你的应用程序很难扩展和维护。 :))