首页 > 其他分享 >使用静态接口方法改进 面向约定 的设计

使用静态接口方法改进 面向约定 的设计

时间:2022-12-07 09:58:05浏览次数:62  
标签:Point 静态 double 接口 面向 var out public result

C# 11带来了一个我期待已久的特性——接口方法。我们知道接口是针对契约的定义,但是一直以来它只能定义一组“实例”的契约,而不能定义类型(的静态成员)的契约,因为定义在接口中的方法只能是实例方法。由于缺乏针对“类型契约”的支持,我们在设计一些框架或者类库的时候,只能采用“按照约定”的设计,比如ASP.NET Core Minimal API针对参数的绑定就是一个典型的案例。以如下这个简单的应用为例,我们采用Minimal API的形式注册了一个针对根地址“/”的路由,作为处理器的委托的输出和输出都是我们自定义的Point对象。

var app = WebApplication.Create();
app.Map("/", (Point point) => point);
app.Run();

public class Point
{
    public double X { get; }
    public double Y { get; }
    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public override string ToString() => $"{X},{Y}";

    public static bool TryParse(string expression, out Point? result)
    {
        result = default;
        var parts = expression.Split(',');
        if (parts.Length != 2) return false;
        if (!double.TryParse(parts[0], out var x) || !double.TryParse(parts[1], out var y)) return false;
        result = new Point(x, y);
        return true;
    }
}

Minimal API的约定,如果我们为Point类型定义了具有如上声明的TryParse方法,该方法就会用来帮助我们绑定处理方法的Point参数,如下的演示结果证实了这一点。

image

其实针对参数绑定,我们还可以定义如下这样BindAsync参数来完成。

public class Point
{
    ...
    public static ValueTask<Point?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
    {
        Point? result = default;
        var name = parameter.Name;
        var value = httpContext.GetRouteData().Values.TryGetValue(name!, out var v) ? v : httpContext.Request.Query[name!].SingleOrDefault();
        if (value is string expression && TryParse(expression, out var point))
        {
            result = point;
        }
        return new ValueTask<Point?>(result);
    }
}

对于这种“基于约定”的编程,可以你觉得还不错,但是我想有90%的ASP.NET Core的开发者不知道有这个特性,就从这一点就充分证明了这样的设计还不够好。这样的实现也比较繁琐,我们不得不通过反射检验待绑定参数的类型是否满足约定,并以反射(或者表达式树)的方式调用对应的方法。其实上述两个方法本应该写入“契约”,无赖它们是静态方法,没法定义在接口中。现在我们有了静态接口方法,它们可以定义如下所示的IBindable<T>和IParsable<T>。

public interface IBindable<T>
{
    abstract static ValueTask<T?> BindAsync(HttpContext httpContext, ParameterInfo parameter);
}

public interface IParsable<T>
{
    abstract static bool TryParse(string expression, out T? result);
}

public class Point : IBindable<Point>, IParsable<Point>
{
    public double X { get; }
    public double Y { get; }
    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public override string ToString() => $"{X},{Y}";

    public static bool TryParse(string expression, out Point? result)
    {
        result = default;
        var parts = expression.Split(',');
        if (parts.Length != 2) return false;
        if (!double.TryParse(parts[0], out var x) || !double.TryParse(parts[1], out var y)) return false;
        result = new Point(x, y);
        return true;
    }

    public static ValueTask<Point?> BindAsync(HttpContext httpContext, ParameterInfo parameter)
    {
        Point? result = default;
        var name = parameter.Name;
        var value = httpContext.GetRouteData().Values.TryGetValue(name!, out var v) ? v : httpContext.Request.Query[name!].SingleOrDefault();
        if (value is string expression && TryParse(expression, out var point))
        {
            result = point;
        }
        return new ValueTask<Point?>(result);
    }
}

实际上IParsable<T>已经存在了,它真正的定义是这样的。如果有了这样的接口,确定带绑定参数类型是否满足之前的约定条件只需要确定其是否实现了对应的接口就可以了。

public interface IParsable<TSelf> where TSelf : IParsable<TSelf>?
{
    static TSelf Parse(string s, IFormatProvider? provider);
    static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TSelf result);
}

静态接口设计被应用到《用最少的代码打造一个Mini版的gRPC框架》中,我在表示gRPC服务的接口中定义了如下的静态方法Bind将本服务类型中定义的gRPC方法绑定成路由。

public interface IGrpcService<TService> where TService : class
{
    static abstract void Bind(IServiceBinder<TService> binder);
}

[GrpcService(ServiceName = "Greeter")]
public class GreeterService: IGrpcService<GreeterService>
{
    public Task<HelloReply> SayHelloUnaryAsync(HelloRequest request, ServerCallContext context);

    public async Task<HelloReply> SayHelloClientStreamingAsync(IAsyncStreamReader<HelloRequest> reader, ServerCallContext context);

    public  async Task SayHelloServerStreamingAsync(Empty request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context);

    public async Task SayHelloDuplexStreamingAsync(IAsyncStreamReader<HelloRequest> reader, IServerStreamWriter<HelloReply> writer, ServerCallContext context);

    public static void Bind(IServiceBinder<GreeterService> binder)
    {
        binder
            .AddUnaryMethod<HelloRequest, HelloReply>(it =>it.SayHelloUnaryAsync(default!,default!), HelloRequest.Parser)
            .AddClientStreamingMethod<HelloRequest, HelloReply>(it => it.SayHelloClientStreamingAsync(default!, default!), HelloRequest.Parser)
            .AddServerStreamingMethod<Empty, HelloReply>(nameof(SayHelloServerStreamingAsync), it => it.SayHelloServerStreamingAsync, Empty.Parser)
            .AddDuplexStreamingMethod<HelloRequest, HelloReply>(nameof(SayHelloDuplexStreamingAsync), it => it.SayHelloDuplexStreamingAsync, HelloRequest.Parser);
    }
}

标签:Point,静态,double,接口,面向,var,out,public,result
From: https://www.cnblogs.com/artech/p/static-interface-method.html

相关文章

  • 函数式接口lambda
    函数式接口Lambda表达式函数式接口定义任何接口,如果只包含唯一一个抽象方法,那么它就是函数式接口publicinterfaceRunnable{publicabstractvoidrun();......
  • DWC PCIE学习笔记(一)----->PCIE PHY接口
    (以下都是PCIE2PHY的各种问题)一、PIPE接口1、PIPE接口用于连接PCIEcontroller和PCIEPHY,controller用PIPE接口发送并行数给PHY用于并串转换等操作,PHY把串并转换得到的并......
  • 面向对象-其他内容
    一、面向对象三大特征封装将属性和方法书写到类的里面的操作即为封装封装可以为属性和方法添加私有权限继承子类默认继承父类的所有......
  • Kubernetes静态Pod
    一、什么是StaticPod静态Pod在指定的节点上由kubelet守护进程直接管理,不需要API服务器监管。与由控制面管理的Pod(例如,Deployment、RC、DaemonSet)不同;kubelet......
  • MeterSphere做登录的接口自动化测试。从调试到自动化脚本
    做登录会遇到的问题1.登录页面的URL和 登录之后的页面URL不一样2.有些值每个接口都会用到,但是每个接口都写一遍很麻烦,而且如果这个值改变了,以后修改起来很麻烦3.后一个脚......
  • ERC1155 OpenZeppelin burnable interface from another contract 不能外部调用burnab
    1.0OpenZeppelin ERC1155BurnableOpenZeppelin有Extensions ERC1155Burnable。如果另一個智能合約需調用ERC1155Burnable時,必需要有IERC1155Burnable接口。但OpenZeppe......
  •  第十二节:优雅方案(反射动态接口实例化、)
    一.        二.        三.         !作       者:Yaopengfei(姚鹏飞)博客地址:http://www.cnblog......
  • 03.Nodejs中的路由与接口
    路由与接口目录目录路由与接口express基本使用托管静态资源nodemonExpress中的路由路由的匹配路由的使用模块化路由Express中间件中间件的格式全局生效的中间件局部生效......
  • VB中的面向对象
    接触过VB的伙伴都知道,VB是一种面向对象的程序设计,那么,什么是面向对象设计、它和其他设计方法有什么区别、它又有什么优点呢?我总结了以下几个方面。首先,面向对象设计的基本概......
  • 在Node.JS中调用JShaman接口,实现JS代码加密
    在Node.JS中调用JShaman接口,实现JS代码加密。使用axios库实现https的post请求,代码如下:constaxios=require("axios");constjshamanConfig={//源码"js_code":......