假设有一群马,我如何找到所有独角兽的平均角长?


30

上面的问题是我在遗留代码中遇到的一个常见问题的抽象示例,或更准确地说,是以前解决该问题的尝试所导致的问题。

我可以想到至少一种旨在解决此问题的.NET框架方法,例如该Enumerable.OfType<T>方法。但是,您最终最终在运行时询问对象的类型这一事实与我不对。

除了问每匹马:“你是独角兽吗?” 还想到以下方法:

  • 尝试获取非独角兽角的长度时引发异常(暴露了不适用于每匹马的功能)
  • 返回一个非独角兽号角长度的默认值或魔术值(需要在所有想要对一组全都是非独角兽的马进行角统计的代码中添加默认检查)
  • 消除继承,并在马上创建一个单独的对象,该对象告诉您马是否为独角兽(这可能会将同一问题推下一层)

我觉得最好用“不回答”来回答。但是,您如何解决这个问题?如果取决于这个问题,您的决定的背景是什么?

对于这个问题是否仍然存在于功能代码中(或者也许仅存在于支持可变性的功能语言中),我也会感兴趣。

这被标记为以下问题的可能重复: 如何避免向下转换?

该问题的答案假设一个人拥有一个HornMeasurer,必须进行所有的号角测量。但这是对基于平等原则而形成的代码库的强加,即每个人都应该可以自由地测量马的角。

缺少a时HornMeasurer,可接受的答案的方法反映了上面列出的基于异常的方法。

关于马和独角兽都是马,还是独角兽是马的神奇亚种,评论中也有些困惑。两种可能性都应该考虑-也许一个比另一个更可取吗?


22
马没有角,因此平均值未定义(0/0)。
Scott Whitlock

3
@moarboilerplate从10到无限大的任何地方。
保姆

4
@StephenP:在这种情况下,这在数学上是行不通的;所有这些0都会使平均值产生偏差。
梅森惠勒

3
如果您的问题最好用不回答来回答,那么它不属于问答网站;reddit,quora或其他基于讨论的网站都是为非答案类型的内容而建的……也就是说,如果您正在寻找@MasonWheeler提供的代码,我认为这显然可以回答,否则我认为我不知道您想问什么..
Jimmy Hoffa

3
@JimmyHoffa“您做错了”恰好是一个可以接受的“无答案”,通常比“好吧,这是您可以做到的一种方式”要好得多–无需进行进一步的讨论。
moarboilerplate

Answers:


11

假设您想将a Unicorn视为一种特殊的Horse,基本上可以通过两种方法对其进行建模。更传统的方式是子类关系。您可以通过简单地重构代码以始终在重要的上下文中使列表始终分开,而仅在您根本不关心Unicorn特征的上下文中将它们组合在一起,从而避免检查类型和向下转换。换句话说,您要安排它,这样就永远不会陷入需要首先从一群马匹中提取独角兽的情况。起初这似乎很困难,但是在99.99%的情况下是可能的,并且通常实际上使您的代码更简洁。

您可以为独角兽建模的另一种方法是给所有马匹提供可选的喇叭长度。然后,您可以通过检查是否有独角兽来测试它是否是独角兽,并通过(在Scala中)找到所有独角兽的平均独角兽长度:

case class Horse(val hornLength: Option[Double])

val horse = Horse(None)
val unicorn = Horse(Some(12.0))
val anotherUnicorn = Horse(Some(6.0))

val herd = List(horse, unicorn, anotherUnicorn)
val hornLengths = herd flatMap {_.hornLength}
val averageLength = hornLengths.sum / hornLengths.size

此方法的优点是:使用单个类更简单明了,但缺点是扩展性差得多,并且具有某种回旋方式来检查“独角兽”。使用此解决方案的诀窍是认识到何时开始对其进行扩展,就需要转向更灵活的体系结构。这种解决方案在功能性语言中更为流行,在这些功能性语言中,您具有简单而强大的功能,例如flatMap可以轻松过滤出None项目。


7
当然,这是假设普通马和独角兽之间的唯一区别是号角。如果不是这种情况,那么事情很快就会变得复杂得多。
梅森惠勒

@MasonWheeler仅在第二种方法中显示。
moarboilerplate 2016年

1
在关于不关心独角兽的上下文中,提出关于在继承情况下如何永远不要将非独角兽和独角兽放在一起的评论。当然,.OfType()可以解决问题并使事情正常进行,但它正在解决的问题本来就不应该存在。至于第二种方法,它之所以奏效,是因为选项远胜于依靠null暗示某些东西。我认为,如果将独角兽特性封装在独立属性中并且非常警惕,则可以在OO中以妥协的方式实现第二种方法。
moarboilerplate

1
如果您将独角兽特质封装在独立的物业中并且非常警惕,那就会妥协 -为什么要让自己的生活艰难。直接使用typeof可以节省大量将来的问题。
gbjbaanb'1

@gbjbaanb我认为该方法仅真正适用于贫血Horse具有角质长度的IsUnicorn属性和某种属性的场景UnicornStuff(当为您的问题中提到的骑手/滑手缩放时)。
moarboilerplate

38

您几乎涵盖了所有选项。如果您的行为依赖于特定的子类型,并且与其他类型混合在一起,则您的代码必须知道该子类型。这是简单的逻辑推理。

我个人来说就是horses.OfType<Unicorn>().Average(u => u.HornLength)。它非常清楚地表达了代码的意图,这通常是最重要的事情,因为某个人最终不得不稍后对其进行维护。


如果我的lambda语法不正确,请原谅我;我不是C#编码员,我也永远无法保持像这样的奥秘细节。不过,应该清楚我的意思。
梅森惠勒

1
不用担心,只要列表仅包含Unicorns(就可以省略记录return),问题就可以得到解决。
moarboilerplate

4
如果我想快速解决问题,这就是我想要的答案。但是,如果我想将代码重构得更合理,那不是答案。
安迪

6
除非您需要一些荒谬的优化级别,否则这绝对是答案。它的清晰度和可读性几乎使其他所有问题无济于事。
大卫说,请恢复莫妮卡(Monica)

1
@DavidGrinberg如果编写此干净,可读的方法意味着您首先必须实现以前不存在的继承结构,该怎么办?
moarboilerplate

9

.NET在以下方面没有错:

var unicorn = animal as Unicorn;
if(unicorn != null)
{
    sum += unicorn.HornLength;
    count++;
}

也可以使用Linq等效项:

var averageUnicornHornLength = animals
    .OfType<Unicorn>()
    .Select(x => x.HornLength)
    .Average();

根据您在标题中提出的问题,这是我希望找到的代码。如果该问题提出类似“有角动物的平均水平”这样的问题,则该问题会有所不同:

var averageHornedAnimalHornLength = animals
    .OfType<IHornedAnimal>()
    .Select(x => x.HornLength)
    .Average();

请注意,当使用Linq时,如果enumerable为空且类型T为不可为空,Average(和MinMax)将引发异常。那是因为平均值确实是不确定的(0/0)。所以确实需要这样的东西:

var hornedAnimals = animals
    .OfType<IHornedAnimal>()
    .ToList();
if(hornedAnimals.Count > 0)
{
    var averageHornLengthOfHornedAnimals = hornedAnimals
        .Average(x => x.HornLength);
}
else
{
    // deal with it in your own way...
}

编辑

我只是认为这需要添加...像这样的问题在面向对象的程序员中无法令人满意的原因之一是,它假定我们正在使用类和对象来对数据结构进行建模。最初的Smalltalk风格的面向对象的想法是将模块构造为模块,这些模块被实例化为对象,并在向它们发送消息时为您提供服务。我们还可以使用类和对象为数据结构建模的事实是(有用的)副作用,但是它们是两回事。我什至不认为后者应该被认为是面向对象的编程,因为您可以使用进行相同的操作struct,但是它不会那么漂亮。

如果您正在使用面向对象的编程来创建为您服务的服务,那么出于充分的理由,通常会询问该服务实际上是其他服务还是具体实现。为您提供了一个接口(通常通过依赖项注入),您应该对该接口/合同进行编码。

另一方面,如果您(错误地)使用类/对象/接口的想法来创建数据结构或数据模型,那么我个人认为充分使用is-a想法不会有问题。如果您已将独角兽定义为马的子类型,并且在您的域内完全有意义,那么绝对可以继续查询您群中的马以找到独角兽。毕竟,在这种情况下,我们通常会尝试创建一种特定于领域的语言,以更好地表达我们必须解决的问题的解决方案。从这个意义上说,.OfType<Unicorn>()etc 没什么错。

最终,获取项目的集合并按类型对其进行过滤实际上只是功能编程,而不是面向对象的编程。幸运的是,像C#这样的语言现在可以轻松地处理这两种范例。


7
您已经知道那animal 是一个Unicorn ;只是强制转换而不是使用as,甚至可能更好地使用as ,然后检查是否为空。
菲利普·肯德尔

3

但是,您最终最终在运行时询问对象的类型这一事实与我不对。

该语句的麻烦在于,无论使用哪种机制,您都将始终在询问对象以告知其类型。可以是RTTI,也可以是您要求的并集或纯数据结构if horn > 0。确切的细节稍有变化,但目的是相同的-您以某种方式询问对象有关其自身的信息,以查看是否应进一步询问该对象。

鉴于此,使用您语言的支持来执行此操作是很有意义的。在.NET中,您将使用typeof例如。

这样做的原因不仅仅在于很好地使用语言。如果您有一个看起来像另一个的物体,但需要一些小的改动,那么随着时间的流逝,您将发现更多差异。在您的独角兽/马匹示例中,您可能会说牛角长度……但是明天您将检查潜在的骑手是否是处女,或者大便是否闪闪发光。(一个经典的真实示例是从通用基础派生的GUI小部件,您必须以不同的方式查找复选框和列表框。差异的数量太大,以至于不能简单地创建一个包含所有可能的数据排列的超级对象。 )。

如果在运行时检查对象的类型不太合适,那么您的替代方法是从一开始就拆分不同的对象-而不是存储一堆独角兽/马,您将拥有2个集合-一个用于马,一个用于独角兽。即使您将它们存储在专门的容器中(例如,以键为对象类型的多重映射),也可以很好地工作,但是即使我们将它们存储在两组中,我们还是会立即询问对象类型!)

当然,基于异常的方法是错误的。使用异常作为正常程序流是一种代码味道(如果您有一群独角兽和一头驴子,其中有一个贝壳贴在头上,那么我想说基于异常的方法是可以的,但是如果您有一群独角兽,然后马匹检查每个人的独角兽就不足为奇了。例外是在特殊情况下,而不是复杂的if陈述)。在任何情况下,针对此问题使用异常只是在运行时询问对象类型,仅在这里您是在滥用语言功能来检查非独角兽对象。您最好在if horn > 0 并至少使用更少的代码行来快速,清晰地处理您的集合,并避免引发其他异常时引发的任何问题(例如,空集合或试图测量驴的贝壳)


在传统环境中,if horn > 0最初解决此问题的方式几乎是这样。然后通常会出现的问题是当您要检查骑手和闪光点时,并horn > 0被埋在无关代码中的各处(由于缺少检查号角是否为0的原因,该代码还存在一些神秘的错误)。另外,在事实之后再归类通常是最昂贵的命题,因此,如果在重构结束时仍将它们归并在一起,我通常不倾向于这样做。因此,它的确确实变成了“替代方案多么丑陋”
moarboilerplate 2016年

@moarboilerplate,您自己说,使用便宜又简单的解决方案,它将变成一团糟。这就是为什么发明OO语言来解决此类问题的原因。一开始,将马换成子类看似昂贵,但很快就收回了成本。随着时间的推移,继续使用简单但混乱的解决方案的成本越来越高。
gbjbaanb

3

由于问题带有functional-programming标签,因此我们可以使用总和类型来反映两种风格的马和模式匹配以消除它们之间的歧义。例如,在F#中:

type Equine =
| Horse
| Unicorn of hornLength: float

module equines =

  let averageHornLength (equines : Equine list) =
    equines 
    |> List.choose (fun x -> 
      match x with
      | Unicorn u -> Some(u)
      | _ -> None)
    |> List.average

let herd = [ Horse ; Horse ; Unicorn(35.0) ; Horse ; Unicorn(50.0) ]

printfn "Average horn length in herd : %f" (equines.averageHornLength herd) // prints 42.5

与OOP相比,FP具有数据/功能分离的优点,这可以使您摆脱(无理?)“罪恶良心”,即当从超类型的对象列表中向下转换到特定的子类型时,它会违反抽象级别。

与其他答案中提出的OO解决方案相比,如果Equine有一天出现另一只有角动物,则模式匹配还可以提供更容易的扩展点。


2

最后,相同答案的简短形式需要阅读本书或网络文章。

访客模式

问题是马与独角兽混在一起。(在传统代码库中,违反Liskov替换原则是一个常见问题。)

为马和所有子类添加方法

Horse.visit(EquineVisitor v)

马访客界面在Java / C#中看起来像这样

interface EquineVisitor {
  void visitHorse(Horse z);
  void visitUnicorn(Unicorn z);
}

Unicorn.visit(EquineVisitor v){
   v.visitUnicorn(this);
}

Horse.visit(EquineVisitor v){
   v.visitHorse(this);
}

为了测量角,我们现在写...。

class HornMeasurer implements EquineVistor {
    void visitHorse(Horse h){} // ignore horses
    void visitUnicorn(Unicorn u){
         double len = u.getHornLength();
         totalLength+=len;
         unicornCount++;
    }

    double getAverageLength(){
          return totalLength/unicornCount;
    }

    double totalLength=0;
    int unicornCount=0;
}

人们批评访问者模式会使重构和增长更加困难。

简短的答案:使用访客设计模式来进行双重调度。

另请参见https://en.wikipedia.org/wiki/Visitor_pattern

有关访问者的讨论,另请参见http://c2.com/cgi/wiki?VisitorPattern

另请参见Gamma等人的《设计模式》。


我本人将用访客模式回答。不得不向下滚动找到一个人是否已经提到它的令人惊讶的方式!
本·瑟利

0

假设在您的建筑中,独角兽是马的亚种,并且您遇到的地方收集了Horse一些它们可能是Unicorns 的集合,那么我个人会采用第一种方法(.OfType<Unicorn>()...),因为这是表达意图的最直接方法。对于后来出现的任何人(包括3个月内的您自己),您将立即发现要使用该代码完成的工作是显而易见的:从马匹中挑选出独角兽。

您列出的其他方法感觉就像是在问“您是独角兽吗?”的另一种方式。例如,如果您使用某种基于异常的方法来测量喇叭,您可能会拥有看起来像这样的代码:

foreach (var horse in horses)
{
    try
    {
        var length = horse.MeasureHorn();
        //...
    }
    catch (NoHornException e)
    {
        continue;
    }
}

因此,现在异常成为指示事物不是独角兽的指示符。现在,这不再是真正的例外情况,而是正常程序流程的一部分。与使用异常相比,使用异常if似乎比仅进行类型检查还要肮脏。

可以说,您走了一条神奇的路线来检查马角。所以现在您的课程看起来像这样:

class Horse
{
    public double MeasureHorn() { return -1; }
    //...
}

class Unicorn : Horse
{
    public override double MeasureHorn { return _hornLength; }
    //...
}

现在,您的Horse班级必须了解该Unicorn班级,并具有其他方法来处理它不关心的事情。现在想象一下,你也有PegasusS和Zebra■从继承Horse。现在Horse需要一种Fly方法,以及MeasureWingsCountStripes等等然后是Unicorn类也得到了这些方法。现在,您的所有类都必须彼此了解,并且已经用一堆不应使用的方法污染了这些类,只是为了避免询问类型系统“这是独角兽吗?”

那么,Horse如果在s上添加一些东西以表示某物为a Unicorn并处理所有喇叭测量,该怎么办?好了,现在您必须检查该对象的存在,才能知道某物是否是独角兽(它只是将一个检查替换为另一个检查)。它也使水有些浑浊,因为现在您可能有一个List<Horse> unicorns确实拥有所有独角兽,但是类型系统和调试器无法轻易告诉您。“但是我知道这全是独角兽,”这个名字甚至是这样说的。好吧,如果某件事名字不好用怎么办?或者说,您写的东西实际上是所有的独角兽,但后来需求发生了变化,现在可能还混入了飞马鱼?(因为从未发生过这种情况,尤其是在旧版软件/ sarcasm中。)现在,类型系统将使您的衣夹成为独角兽的快乐对象。如果您List<Unicorn>尝试将变量混入pegasi或horse中,则将变量声明为编译器(或运行时环境)将是合适的。

最后,所有这些方法只是类型系统检查的替代。就我个人而言,我不想在这里重新发明轮子,希望我的代码能够像内置的东西一样工作,并且已经被成千上万的其他编码人员测试了数千次。

最终,代码必须是可以理解的。不管您如何编写,计算机都会弄清楚。您是必须调试它并能够对此进行推理的人。做出使您的工作更轻松的选择。如果由于某种原因,这些其他方法中的一种为您提供了一个优势,那就是它会在几个显示的地方胜过更清晰的代码。但这取决于您的代码库。


无声异常绝对是不好的-我的建议是进行检查,不会if(horse.IsUnicorn) horse.MeasureHorn();捕获异常-它们将在!horse.IsUnicorn您处于独角兽测量环境中时触发,或者在MeasureHorn非独角兽内部触发。这样,当引发异常时,您不会掩盖错误,它会完全炸毁,这是需要修复的迹象。显然,它仅适用于某些情况,但它是一种不使用异常抛出来确定执行路径的实现。
moarboilerplate 2016年

0

好吧,这听起来好像您的语义域具有IS-A关系,但是您对于使用子类型/继承对此建模感到有些警惕-尤其是由于运行时类型反射。但是,我认为您担心错误的事情-子类型化确实有危险,但是在运行时查询对象这一事实并不是问题。您会明白我的意思的。

面向对象的程序设计在很大程度上依赖于IS-A关系的概念,可以说它过于依赖它,导致了两个著名的关键概念:

但是我认为,还有另一种基于功能编程的方式来查看IS-A关系,这些方法也许没有这些困难。首先,我们要在程序中对马和独角兽进行建模,因此我们将拥有HorseUnicorn类型。这些类型的值是什么?好吧,我这样说:

  1. 这些类型的值分别是马和独角兽的表示描述
  2. 它们是模式化的表示或描述-它们不是自由格式,而是根据非常严格的规则构造的。

这听起来似乎很明显,但是我认为人们陷入诸如圆椭圆问题之类的方法之一就是没有足够仔细地考虑这些问题。每个圆都是一个椭圆,但这并不意味着圆的每个模式化描述都会自动根据不同的模式对椭圆进行模式化描述。换句话说,仅仅说一个圆是一个椭圆并不意味着a Circle是一个Ellipse。但这确实意味着:

  1. 有一个总功能可以将任何Circle(化学式的圆圈描述)转换Ellipse为描述相同圆圈的(不同类型的描述);
  2. 有一个带和的偏函数Ellipse,如果描述一个圆,则返回相应的Circle

因此,用函数式编程术语来说,您的Unicorn类型根本不需要是其子类型Horse,您只需要执行以下操作:

-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse

-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn

并且toUnicorn必须是toHorse

toUnicorn (toHorse x) = Just x

Haskell的Maybe类型是其他语言称为“选项”类型的类型。例如,Java 8 Optional<Unicorn>类型为an Unicorn或为空。请注意,您的两个选择(引发异常或返回“默认值或魔术值”)与选项类型非常相似。

因此,基本上,我在这里所做的是在不使用子类型或继承的情况下,根据类型和功能来重建概念IS-A关系。我从中得到的是:

  1. 您的模型需要有一个Horse类型;
  2. Horse类型需要编码足够的信息,以便明确确定是否有任何值描述了独角兽。
  3. Horse类型的某些操作需要公开该信息,以便该类型的客户可以观察给定的对象Horse是否为独角兽。
  4. Horse类型的客户端将必须在运行时使用后面的这些操作来区分独角兽和马匹。

因此,从根本上讲,这是一个“问Horse是否是独角兽”的模型。您对该模型保持警惕,但我认为是错误的。如果我给您一个Horses 列表,则该类型所保证的全部是列表中各项所描述的都是马,因此,您不可避免地需要在运行时执行某些操作以告诉他们哪个是独角兽。因此,我认为这是无可避免的-您需要实施能够为您做到这一点的操作。

在面向对象的编程中,熟悉的方法如下:

  • Horse类型;
  • Unicorn作为的一个亚型Horse;
  • 使用运行时类型反射作为客户端可访问的操作,该操作可识别给定Horse是否为Unicorn

从我上面介绍的“事物与描述”的角度来看,这确实有一个很大的弱点:

  • 如果您有一个Horse描述独角兽的Unicorn实例而不是一个实例怎么办?

回到开始,这是我认为使用子类型化和下流式建模此IS-A关系的真正可怕的部分,而不是必须进行运行时检查的事实。稍微滥用字体,询问它Horse是否是Unicorn实例并不等于询问它Horse是否是独角兽(是否是Horse对马的描述也是独角兽)。除非您的程序竭尽全力以封装所构造的代码,否则Horses每次客户端尝试构造Horse描述独角兽的a时,Unicorn该类都将实例化。以我的经验,程序员很少会认真地做这些事情。

因此,我将采用一种方法,其中存在将Horses 转换为Unicorns 的显式,非向下转换操作。这可以是以下Horse类型的方法:

interface Horse {
    // ...
    Optional<Unicorn> toUnicorn();
}

...或者它可能是一个外部对象(您的“马的单独对象,告诉您马是否为独角兽”):

class HorseToUnicornCoercion {
    Optional<Unicorn> convert(Horse horse) {
       // ...
    }
}

两者之间的选择Horse -> Maybe Unicorn取决于程序的组织方式–在两种情况下,您都可以从上面获得与我相同的操作,只是以不同的方式打包(这无疑会对Horse类型所需的操作产生连锁反应)向客户展示)。


-1

我认为OP在另一个答案中的评论澄清了这个问题

这也是问题的一部分。如果我有一群马,其中有些在概念上是独角兽,应该如何存在它们,以便可以在没有太多负面影响的情况下彻底解决问题?

这样说吧,我认为我们需要更多信息。答案可能取决于许多因素:

  • 我们的语言设施。例如,在ruby和javascript和Java中,我可能会采取不同的方法。
  • 概念本身:什么马,什么独角兽?每种数据都有哪些关联?除了号角外,它们是否完全相同,还是有其他差异?
  • 除了求牛角长度平均值外,我们还如何使用它们?那群呢?也许我们也应该对它们建模?我们在其他地方使用它们吗? herd.averageHornLength()似乎符合我们的概念模型。
  • 如何创建马和独角兽对象?是否在重构的范围内更改了代码?

通常,尽管如此,我什至不会在这里考虑继承和子类型。您有一个对象列表。这些对象中的某些可以被识别为独角兽,也许是因为它们具有某种hornLength()方法。基于此unicorn-unique属性过滤列表。现在,问题已经减少到平均独角兽列表的喇叭长度。

OP,让我知道我是否仍然误会...


1
公平点。为了避免问题变得更加抽象,我们必须做出一些合理的假设:1)一种强类型的语言2)畜群可能将马限制为一种类型,这可能是由于集合3)可能应该避免使用类似鸭子类型的技术。至于可以更改的内容,不一定有任何限制,但是每种类型的更改都有其独特的后果……
moarboilerplate 2016年

如果畜群将马限制为一种类型,则不是我们唯一的选择继承(不喜欢该选项)还是HerdMember我们用马或独角兽初始化的包装对象(例如)(使马和独角兽摆脱了子类型关系) )。 HerdMember然后可以自由实施,isUnicorn()但它认为合适,我建议采用过滤解决方案。
乔纳

在某些语言中,可以混合使用hornLength(),如果是这样,它可能是有效的解决方案。但是,在打字不太灵活的语言中,您必须借助一些技巧来完成相同的操作,或者您必须像在马上放置hornlength这样会导致代码混乱的方法,因为马不会这样做。从概念上讲,它没有任何角。另外,如果进行数学计算(包括默认值)可能会使结果偏斜(请参阅原始问题下的评论)
moarboilerplate

但是,除非在运行时完成,否则Mixins只是继承另一个名称。您的评论“一匹马在概念上没有任何角”与我的评论有关,如果我们的答案需要包括我们如何对马和独角兽进行建模以及它们之间的关系,则需要更多地了解它们是什么。任何包含默认值的解决方案都是错误的imo。
乔纳

您的要求是正确的,因为您需要针对此问题的具体表现找到精确的解决方案,您需要具有很多背景信息。为了回答有关带角马的问题并将其与mixins联系起来,我想到了一种情况,将hornLength混入不是独角兽的马中是错误的。考虑一个Scala特征,它具有hornLength的默认实现,该实现会引发异常。独角兽类型可以覆盖该实现,并且如果一匹马曾经进入评估hornLength的上下文,这是一个例外。
moarboilerplate

-2

对于我来说,返回IEnumerable的GetUnicorns()方法似乎是最优雅,灵活和通用的解决方案。这样,您可以处理决定马是否会作为独角兽通过的所有(组合)特征,而不仅仅是类类型或特定属性的值。


我同意这一点。梅森·惠勒(Mason Wheeler)也提供了一个很好的解决方案,但是如果出于各种原因在不同地方选择独角兽,您的代码将具有很多horses.ofType<Unicorn>...构造。具有GetUnicorns功能将是单线的,但从调用者的角度来看,它将进一步抵抗马/独角兽关系的变化。
Shaz

@Ryan如果您返回一个IEnumerable<Horse>,尽管您的独角兽条件在一个地方,但它是封装的,所以您的呼叫者必须做出为什么他们需要独角兽的假设(今天我可以点当天的汤来弄杂蛤cho,但是事实并非如此)并不意味着我明天会做同样的事情。另外,您必须在上公开喇叭的默认值Horse。如果Unicorn是它自己的类型,则必须创建一个新类型并维护类型映射,这可能会带来开销。
moarboilerplate

1
@moarboilerplate:我们认为所有对解决方案的支持。美的部分是它独立于独角兽的任何实现细节。无论您是根据数据成员,班级还是一天中的时间进行区分(如果我所知道的月亮都正确,那些马可能在午夜时分都变成独角兽),那么解决方案将始终保持不变。
马丁·马特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.