2014-03-27 58 views
4

我是f#的新手。我想将搜索结果列表返回到前端。然而,可能存在几种类型的搜索结果(例如,BlogResult,MovieResult等),每个搜索结果具有它们自己的属性(例如MovieResult.PosterIcon)。用户界面将知道这些类型,并相应地显示每种类型。核心搜索API不会知道这些类型,只提供一个“提供者”需要实现的框架,包括它将返回的类型。例如,BlogProvider将实现必要的功能,并且它是相应的搜索结果类型。应用程序会将所有这些提供程序一起注册到核心框架中,以便所有结果都应按排名顺序作为单个项目列表返回。这些提供程序很可能分别存在于自己的程序集中,与应用程序和核心搜索API分开。继承是解决这个问题的正确方法吗?

我的面向对象的大脑想要有一个SearchResult类或接口,所有其他结果类型继承。有没有更好的方法来处理这个问题?

更新 该应用程序是一个ASP.NET MVC应用程序。每个结果都由局部视图表示。当添加新的搜索结果类型时,网站本身不需要修改。只有新的局部视图。这并不违反开放/封闭原则。从技术上讲,我可以让提供者为局部视图创建模板。然而,仪式代码是不值得的。

+0

@ user2864740一个接口只不过是一个荣耀的抽象类,根本没有任何实现。我的问题不在于如何实施我的API和它的运营合同。这是关于如何实现我的数据结构。 –

+0

@ user2864740 - 我知道所有关于正确的OO设计原则。我在问适当的函数式编程设计原则。这是我的问题的目的。我不是在这里挑选一个类和一个接口之间的区别。我的观点是,使用继承,无论是类还是接口,都是我知道的唯一选择。在f#中,由于继承要求,使用标准的.NET类或接口排除了供应商使用歧视联合或记录类型的可能性。如果我没有弄错,在某些情况下也会消除类型推断 –

+0

如果您对“帮助”非常认真*,请包括相关/建议结构。这个问题再一次不是与“纯粹功能性”相关的。 – user2864740

回答

2

这可能是我有限的想象力和糟糕的设计技巧,但每当我被提出像上面这样的要求时,我就不得不做出某种妥协。这就是为什么在OOP中也很困难:

UI将知道这些类型,并将相应地显示每种类型。

这是有问题的部分。至于我可以告诉大家的话,只有两种方法来满足这一要求:

  • 把所有的“子类型”为特例。如果您使用OOD来完成,这意味着客户端(UI)必须了解所有可用的子类型。它可以以临时方式尝试向下转发,也可以利用Visitor pattern,但无论在哪种情况下,都会违反Open/Closed Principle,因为您不能在不修改UI代码的情况下向系统添加新的“提供程序”。
  • 让所有“子类型”实现通用接口或从公共基类继承。这个接口(或基类)然后将有一个(或多个)UI可以调用来呈现结果的方法。这种方法可称为渲染,这将是技术特定:
    • 对于桌面应用程序,它可能看起来像这样void Render(Canvas)(简体) - 也就是说,它接收某种帆布对象的,在然后要求它进行渲染。
    • 对于Web应用程序,它可能是一个返回HTML片段的函数:string Render()(再次简化)。

虽然可以拿出更详细的方案,他们会往往是上述两种选择的一种结合。

每个替代方法都可以用F#建模,而不依赖于继承。

特殊情况

不是把每个“子类型”作为一个特殊的情况下,你可以定义一个区分联合所有的各种情况:

open System 

type BlogResult = { 
    Title : string 
    Summary : string } 

type MovieResult = { 
    Title : string 
    PosterIcon : Uri } 

type SearchResult = 
    | BlogResult of BlogResult 
    | MovieResult of MovieResult 

这样做的如果你想引入一个新的子类型,你需要修改(重新编译)UI。另一方面,歧视联盟内置于F#,易于使用,甚至可以为您提供编译时检查(您也可以使用访问者模式获得)。

通用接口

作为替代使用一个通用的接口,你可以使用一个功能。功能,或者说,closures are equivalent to objects

因此,不要让UI消耗定义了string Render()方法的接口,而是可以让UI使用具有以下签名的函数:unit -> string

任何你想要插入系统的'提供者'只需要返回一个带有这个签名的函数,用于每个搜索结果。这个函数可能是一个闭包。

+0

请参阅我的编辑参考打开/关闭原则违规问题 –

+0

在后一种情况下,为什么不只是让提供程序自己返回呈现的字符串呢? –

+0

@DaxFohl我不确定我理解你的问题;是不是我建议的功能(单元 - >字符串)呢? –

2

声明:此答案不适用于F#。马克的答案是最好的F#提供。特别是有区别的联合方法,C#没有直接的等价物。

但你问“功能”,和其他一些功能语言有更好的选择。其他功能语言,如Scala和Haskell提供了一个名为typeclasses的特性,它基本上可以使扩展方法实现一个接口,然后在事实之后再使用该接口。

那么这是如何相关?那么用F#你必须要么A)预先确定你在判别联盟中的所有案例,要么B)在你的数据库中放置渲染功能。两者都不是理想的。

随着类型类,你可以单独定义的记录类型:

// Blog.fs 
type Blog {...} 
[<SomeProviderAttribute>]let getBlogs() = ... // returns Blog seq 

// Movie.fs 
type Movie = {...} 
[<SomeProviderAttribute>]let getMovies() = ... // returns Movie seq 

然后,你可以在更高层次上的接口箭:(伪代码如下)

// UiBase.fs 
class Renderable where render :: 'T -> string 

// BlogUi.fs 
instance Renderable Blog where render blog = blog.header + blog.text 

// MovieUi.fs 
instance Renderable Movie where render movie = movie.title + movie.description 
从主UI区域

最后收集渲染的字符串很容易。如果您打开定义类型类的模块,你可以互换使用它们:

// MainUi.fs 
open Blog; open Movie; open UiBase; open BlogUi; open MovieUi 
// Just like you'd have to call "using" in C# to get the extention methods, 
// you have to open these to get the type classes 

let dataProviders = ... // Some assembly search and reflection here; 
        // say it returns [getBlogs; getMovies] 
        // (and note without the common typeclass, 
        // those two would be incompatible) 

let renderedStrings = 
    seq { for provider in dataProviders do 
      for renderable in provider() do 
      yield render renderable } 

所以基本上可以让你使用(否则完全无关)互换上级博客和电影类和供应商,是因为你能够“中断”中间层的界面。非常非常酷,适合你在这里遇到的常见问题。

...不幸的是F#本身没有这个功能。

相关问题