【问题标题】:How can I DRY out this F# code? (Fluent Interface)我怎样才能干掉这个 F# 代码? (流畅的界面)
【发布时间】:2016-08-02 15:07:53
【问题描述】:

所以这是我写过的最潮湿的代码。但它很有用,这很烦人。所有重复的原因是因为我想保持界面流畅。如果我扩充基类(在这种情况下恰好是 View),它只会返回一个 View 的实例,这将阻止我做类似的事情

let label = theme.CreateLabel().WithMargin(new Thickness(5.0)).WithText("Hello")

因为Label.Text 属性未由View 基类实现。

所以这是我的流畅界面。做好准备。这很丑陋,而且重复。但它也有效,而且使用方便。

我是否错过了一种明显的干燥方法?

module ViewExtensions =
    let private withTwoWayBinding<'TElement, 'TProperty, 'TViewModel, 'TView when 'TView :> IViewFor<'TViewModel>>(viewModel: 'TViewModel, view: 'TView, viewModelProperty: Expr<'TViewModel -> 'TProperty>, viewProperty: Expr<'TView -> 'TProperty>) (element: 'TElement) = 
        view.Bind(viewModel, ExpressionConversion.toLinq viewModelProperty, ExpressionConversion.toLinq viewProperty) |> ignore
        element
    let private withHorizontalOptions<'TElement when 'TElement :> View> options (element: 'TElement) =
        element.HorizontalOptions <- options
        element
    let private withVerticalOptions<'TElement when 'TElement :> View> options (element: 'TElement) =
        element.VerticalOptions <- options
        element
    let private withAlignment<'TElement when 'TElement :> View> horizontalOptions verticalOptions (control: 'TElement) =
        control |> withHorizontalOptions horizontalOptions |> withVerticalOptions verticalOptions
    let private withMargin<'TElement when 'TElement :> View> margin (element: 'TElement) = 
        element.Margin <- margin
        element
    let private withActions<'TElement> (actions: ('TElement -> unit)[]) (element: 'TElement) = 
        for action in actions do action(element)
        element
    type Xamarin.Forms.Entry with 
        member this.WithHorizontalOptions(options) = withHorizontalOptions options this
        member this.WithVerticalOptions(options) = withHorizontalOptions options this
        member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
        member this.WithTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) = withTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) this
        member this.WithMargin(margin) = withMargin margin this
        member this.With(actions) = withActions actions this
        member this.With(action: Entry -> unit) = this.With([|action|])
    type Xamarin.Forms.Grid with 
        member this.WithHorizontalOptions(options) = withHorizontalOptions options this
        member this.WithVerticalOptions(options) = withHorizontalOptions options this
        member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
        member this.WithMargin(margin) = withMargin margin this
        member this.With(actions) = withActions actions this
        member this.With(action: Grid -> unit) = this.With([|action|])
    type Xamarin.Forms.StackLayout with 
        member this.WithHorizontalOptions(options) = withHorizontalOptions options this
        member this.WithVerticalOptions(options) = withHorizontalOptions options this
        member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
        member this.WithMargin(margin) = withMargin margin this
        member this.With(actions) = withActions actions this
        member this.With(action: StackLayout -> unit) = this.With([|action|])
    type Xamarin.Forms.Button with 
        member this.WithHorizontalOptions(options) = withHorizontalOptions options this
        member this.WithVerticalOptions(options) = withHorizontalOptions options this
        member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
        member this.WithMargin(margin) = withMargin margin this
        member this.WithText(text) = this.Text <- text; this
        member this.With(actions) = withActions actions this
        member this.With(action: Button -> unit) = this.With([|action|])
    type Xamarin.Forms.Switch with 
        member this.WithHorizontalOptions(options) = withHorizontalOptions options this
        member this.WithVerticalOptions(options) = withHorizontalOptions options this
        member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
        member this.WithTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) = withTwoWayBinding(viewModel, view, viewModelProperty, viewProperty) this
        member this.WithMargin(margin) = withMargin margin this
        member this.With(actions) = withActions actions this
        member this.With(action: Switch -> unit) = this.With([|action|])
    type Xamarin.Forms.Label with 
        member this.WithHorizontalOptions(options) = withHorizontalOptions options this
        member this.WithVerticalOptions(options) = withHorizontalOptions options this
        member this.WithAlignment(horizontalOptions, verticalOptions) = withAlignment horizontalOptions verticalOptions this
        member this.WithMargin(margin) = withMargin margin this
        member this.WithText(text) = this.Text <- text; this
        member this.With(actions) = withActions actions this
        member this.With(action: Label -> unit) = this.With([|action|])

更新

感谢您的帮助,答案是肯定的,我遗漏了一些明显的东西。正如 TheQuickBrownFox 解释的那样,如果我将流畅的界面更改为某种形式

let label = theme.CreateLabel() |> withMargin(new Thickness(5.0)) |> withContent("Hello")

那么你上面看到的怪物可以全部替换为

module ViewExtensions =
    let withTwoWayBinding<'TElement, 'TProperty, 'TViewModel, 'TView when 'TView :> IViewFor<'TViewModel>>(viewModel: 'TViewModel, view: 'TView, viewModelProperty: Expr<'TViewModel -> 'TProperty>, viewProperty: Expr<'TView -> 'TProperty>) (element: 'TElement) = 
        view.Bind(viewModel, ExpressionConversion.toLinq viewModelProperty, ExpressionConversion.toLinq viewProperty) |> ignore
        element
    let withHorizontalOptions options (element: #View) = element.HorizontalOptions <- options; element
    let withVerticalOptions options (element: #View) = element.VerticalOptions <- options; element
    let withAlignment horizontalOptions verticalOptions element = element |> withHorizontalOptions horizontalOptions |> withVerticalOptions verticalOptions
    let withMargin margin (element: #View) = element.Margin <- margin; element
    let withCaption text (element: #Button) = element.Text <- text; element
    let withText text (element: #Entry) = element.Text <- text; element
    let withContent text (element: #Label) = element.Text <- text; element
    let withSetUpActions<'TElement> (actions: ('TElement -> unit)[]) (element: 'TElement) = (for action in actions do action(element)); element
    let withSetUpAction<'TElement> (action: 'TElement -> unit) = withSetUpActions([|action|])

这个代码删除确实很讨喜。

【问题讨论】:

  • 你不能做后缀应用程序(又名“管道转发”)而不是扩展方法吗?这样你就可以用约束使函数通用。
  • 我不得不承认,在我阅读 TheQuickBrownFox 的答案之前,我不太清楚你的意思。现在完全有道理。它工作得非常好。更新待定。

标签: f# xamarin.forms


【解决方案1】:

流畅接口的惯用 F# 方法就是使用管道转发运算符|&gt;

module ViewHelpers
    let withMargin margin element = ...
    let withText text element = ...

open ViewHelpers

let label = theme.CreateLabel() |> withMargin (new Thickness(5.0)) |> withText "Hello"

我认为您还可以使用 flexible types 缩短函数签名:

let withMargin margin (element: #View) = ...

【讨论】:

  • 这听起来很棒。如果一切顺利,我会尽快尝试并将其标记为答案。正如 F# 经常发生的那样,我一直在学习。灵活类型是一个绝妙的主意,直到现在我才听说过。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 2018-02-22
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
  • 2018-08-29
相关资源
最近更新 更多