用可视化案例讲Rust编程4. 用泛型和特性实现自适配shapefile的读取

news2024/11/16 4:34:30

本节已经涉及Rust学习曲线上的一个大坑:泛型和特性了,属于语言的深水区,如果初学者,建议看一眼知道有这个功能即可。

如果我们立足于功能实现,那么做到像上一节那样就可以了,从原理上来说,每个函数满足唯一的功能,是一种好的设计,软件工程里面“高内聚低耦合”是有利于系统的独立性的。

但是在调用的时候,就需要有一点的心智了,例如我们要读取点线面三种不同的shapefile,就要记住三种不同的函数名和三种不同的绘图要素构建程序,那也太麻烦了,有没有一种方法,让我们调用一种方法,根据不同的参数类型自动适配不同的处理逻辑呢?

如果你是学Python/Java/JavaScript这样的高级语言的同学,你肯定已经有了自己的实现方法,例如Python/Javascript作为动态类型的语言,可以只写一个入口函数,然后根据函数的输入参数自动去选择不同的分支来处理。

而Java也有同名方法覆盖,利用不同的函数签名(参数类型)来解决用同名函数实现不同的逻辑流程。

而C++则是可以用泛型和函数重载来实现这个功能。

那么在Rust里面如何解决呢?

  • 首先,Rust不支持函数签名的类型推断。所有函数签名的类型必须写清楚,这就杜绝了我们用动态类型的语言那样根据输入的参数来匹配。
  • 第二,Rust也不(直接)支持函数重载,为什么是不直接支持呢,因为可以通过泛型和特性来实现函数重载,也就是我们今天要做的内容。

老规矩,先看结果:

img

用同名的一个方法,实现对于三种不同的shapefile读取,并且获取三种不同的返回类型值,那么这是这么实现的呢?

先看代码:

架构定义

  • 首先定义了一个结构体,这个空结构体的作用类似于Java里面的class。
  • 然后定义了一个特性(trait),特性这个东西,在Rust中,类似于Java里面的接口,或者C++里面的虚函数,但是与接口和虚函数不同的时候,特性可以直接在里面写实现,也可以留空,如果你是学Java的,你把下面的代码理解为定义了一个接口,并且定义了一个实现接口的工厂方法即可。
  • 在这里的特性中,我们定义一个泛型T,然后定义了一个方法,就叫做read_shp,输入产生就是一个shapefile的路径,返回值是一个T ,这个T就是Rust中的泛型,即代表一切可能的类型。
    • 如果是没有接触过泛型的同学,可能会问,他既然代表一切类型,那么你不是说了Rust是类型严格的语言么?那系统编译器怎么知道,这个T到底是哪种类型呢?继续往下看
pub struct shp;
pub trait ReadShapfile<T> {
    fn read_shp(shp_path:&str) -> T;
}

然后我们开始写针对不同类型的shapefile的实现,实现如下:

//实现1:读取polygon类型的shapefile,通过返回值来匹配。
impl ReadShapfile<Vec<Polygon>> for shp{
    fn read_shp(shp_path:&str) -> Vec<Polygon>{
        let shp_read = shapefile::read_as::<_,
        shapefile::Polygon, shapefile::dbase::Record>(shp_path)
        .expect(&format!("Could not open polygon-shapefile, error: {}", shp_path));

    let mut polygons:Vec<Polygon> = Vec::new();
    for (polygon, polygon_record) in shp_read {
        let geo_mpolygon: geo_types::MultiPolygon<f64> = polygon.into();
        for poly in geo_mpolygon.iter(){
            polygons.push(poly.to_owned());
        }
    }
    polygons
    }
}

//实现2:读取Polyline类型的shapefile,通过返回值来匹配。
impl ReadShapfile<Vec<LineString>> for shp{
    fn read_shp(shp_path:&str) -> Vec<LineString>{
        let shp_read = shapefile::read_as::<_,
            shapefile::Polyline, shapefile::dbase::Record>(shp_path)
        .expect(&format!("Could not open polyline-shapefile, error: {}", shp_path));

        let mut linestrings:Vec<LineString> = Vec::new();
        for (pline, pline_record) in shp_read {
            let geo_mline: geo_types::MultiLineString<f64> = pline.into();
            for line in geo_mline.iter(){
                linestrings.push(line.to_owned());
            }
        }
        linestrings
    }
}
//实现2:读取Point类型的shapefile,通过返回值来匹配。
impl ReadShapfile<Vec<Point>> for shp{
    fn read_shp(shp_path:&str) -> Vec<Point>{
        let shp_read = shapefile::read_as::<_,
            shapefile::Point, shapefile::dbase::Record>(shp_path)
        .expect(&format!("Could not open polyline-shapefile, error: {}", shp_path));

        let mut pnts:Vec<Point> = Vec::new();
        for (pnt, pnt_record) in shp_read {
            let geo_pnt: geo_types::Point<f64> = pnt.into();
            pnts.push(geo_pnt.to_owned());
        }
        pnts
    }
}

简要的解释一下:

  • 在Rust的语法中,impl 特性名称<返回值> for 结构名 这个语法,代表了具体的实现,而里面那个 <返回值> 部分,就是我们定义的泛型T,注意,T只是一个代称,习惯性用大写字母,你用ABCDEFG,或者abcdefg都是可以的。

  • 然后在这个实现里面,去写具体方法的read_shp的实现,写完之后,就可以通过在调用的时候,再指定返回类型,来适配具体调用哪个方法了。

看到这里,可能有同学又有问题了:

上一节,直接写多个方法,多实用啊,读polygon就是polygon方法,读point就是point方法,干净整洁又高效,干嘛搞这么麻烦?

img

实际上两种方式,都是可以的,每个方法用独立名称进行调用,实际上是一种比较传统,但是很高效的方式(C语言里面写操作系统内核都是这样写的)。

而后面这种,则是现代编程架构里面推荐的,用同名函数,来减少调用者的心智负担,反正都是读取shapefile,我干嘛还要记那么多个函数名啊?为什么不能用一个函数就全部解决了呢?

img

所以在软件开发中,同名函数,通过定义不同的返回值,或者输入值来区分的方式,叫做多态,而在Rust里面,多态又可以分为静态分发:即静态多态模式或者单一态模式;以及动态分发:即动态多态模式

这种所谓的静态动态,在Rust中,是针对编译而言的,静态即代码和方法,在被编译的时候,就已经决定了编译的结果,你的输入、输出、处理流程等,都在编译的时候被固定下来了。

这种也是函数式编程的核心思想:函数式编程强调:不可变性和纯函数,这意味着函数的输出只取决于它的输入,而不依赖于外部状态

动态则相反,在编译的时候,系统并不知道要输入的参数和输出的结果具体是什么类型,只有在运行的时候,根据输入的数据情况才知道。

一般来说,动态语言中的动,就是这种,通常解释性的语言,都是动态的,例如Python/Javascript等。

当然,Rust也是支持动态编译的,但是在使用动态编译的时候,需要有一系列的手续,这个我们以后再说。

从两种编译模式来看,静态模式的开销小,安全性高,但是灵活性比较差;反之,动态模式灵活性好,但是开销大,安全性和稳定性较差。

具体在开发过程中,用哪种模式,就仁者见仁智者见智了。

最后我们来画一个UML图,就很容易看出来这个架构的实现思路了:

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

javascript跳转微信: 扫码,分享链接,下载二维码

文章目录 微信后台配置扫码前端配置扫描二维码分享链接下载二维码 总结 微信后台配置扫码 参考&#xff1a; 微信二维码内容获取 在开发设置中配置扫码路由以及跳转路径 完成该配置后&#xff0c;可以通过扫描对应地址的二维码进入小程序的指定页面 前端配置 扫描二维码 …

【github】使用github action 拉取国外docker镜像

使用github action 拉取国外docker镜像 k8s部署经常用到国外镜像&#xff0c;如果本地无法拉取可以考虑使用github action环境 github action的ci服务器在国外&#xff0c;不受中国防火墙影响github action 自带docker命令运行时直接将你仓库代码拉取下来 步骤 你的国内dock…

测试人年终总结:入行三年,下一步怎么走,思想碰撞

原贴地址&#xff1a;入行三年&#xff0c;下一步怎么走&#xff0c;思想碰撞 TesterHome 熟悉环境&#xff0c;进步缓慢&#xff1b;停止思考&#xff0c;举步不前&#xff08;为什么会有这篇文章why 初心变质&#xff1a;计算机系毕业&#xff0c;毕业时的打算是从测试进&a…

如何实现人口、GDP等的空间化效果?

现在假设你遇到了某个情况&#xff0c;手头有某市十几个区县的人口总数但是你想要更加精细化的空间化数据产品&#xff0c;以方便后续进行核密低分析或者进行其它的空间、数据分析。 那我们应该如何通过十几个点的人口数据进行空间化呢&#xff0c;详细步骤如下&#xff1a; 第…

PHP“引用”漏洞

今日例题&#xff1a; <?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); class just4fun { var $enter; var $secret; } if (isset($_GET[pass])) { $pass $_GET[pass]; $passstr_replace(*,\*,$pass); } $o unser…

Flume介绍

一、介绍 Apache Flume 是一种分布式、可靠且可用的系统&#xff0c;用于有效地收集、汇总大量日志数据&#xff0c;并将其从多个不同来源转移到集中式数据存储区。 Apache Flume 的使用不仅限于日志数据聚合。由于数据源是可定制的&#xff0c;Flume 可用于传输大量事件数据&a…

OpenCV书签 #互信息的原理与相似图片搜索实验

1. 介绍 互信息&#xff08;Mutual Information&#xff09; 是信息论中的一个概念&#xff0c;用于衡量两个随机变量之间的关联程度。在图像处理和计算机视觉中&#xff0c;互信息常被用来度量两幅图像之间的相似性。 互信息可以看成是一个随机变量中包含的关于另一个随机变…

腾讯云tsf平台-部署微服务项目

腾讯云tsf平台-部署微服务项目 一、腾讯云tsf平台简介二、部署准备0&#xff08;数据库、中间件等部署&#xff09;三、部署准备1&#xff08;创建集群和命名空间&#xff09;1、准备部署资源--集群2、使用容器部署微服务步骤 1&#xff1a;创建容器集群步骤 2&#xff1a;创建…

数据结构:非完全二叉树(递归实现)

非完全二叉树是指在二叉树中&#xff0c;除了叶子节点&#xff08;无子节点&#xff09;外&#xff0c;其他节点的子节点个数可以不同&#xff0c;即不一定是每个节点都有两个子节点&#xff0c;有右孩子时也不一定有左孩子。 tree.h /* * 文件名称&#xff1a;tree.h * …

SpringBootAdmin邮件通知

在上一篇中我们学习到了 Admin Service Clustering 分布式缓存配置 &#xff0c;这一篇我们来学习&#xff0c;客户端离线&#xff0c;出现故障的时候&#xff0c;我们这么能及时知道呢&#xff0c;发现邮件通知也许是最简单的方式了&#xff01; 邮件通知 邮件通知将作为使用…

代码随想录 Leetcode111. 二叉树的最小深度

题目&#xff1a; 代码(首刷自解 2024年1月24日&#xff09;&#xff1a; class Solution { public:int minDepth(TreeNode* root) {if(root nullptr) return 0;queue<TreeNode*> que;TreeNode* cur root;que.push(cur);int size 0;int depth 0;while (!que.empty()…

【方法】如何打开7Z格式压缩文件?

7Z是一种常见的压缩文件格式&#xff0c;那遇到7Z压缩文件&#xff0c;要如何打开呢&#xff1f; 想要打开7Z压缩包&#xff0c;需要用到解压缩软件&#xff0c;比如WinRAR、7-Zip等软件。安装软件后&#xff0c;只需要用鼠标双击7Z压缩包&#xff0c;就可以打开压缩包&#x…

高数总结(3

目录 1.总结&#xff1a;小结&#xff1a; 1.总结&#xff1a; 小结&#xff1a; 关注我给大家分享更多有趣的知识&#xff0c;以下是个人公众号&#xff0c;提供 ||代码兼职|| ||代码问题求解|| 由于本号流量还不足以发表推广&#xff0c;搜我的公众号即可&#xff1a;

ECharts实现简单饼图和柱状图

1.JAVA 1.饼图 前端使用vue&#xff0c;后端使用SpringBoot <template><div><div class"card" style"padding: 15px">数据可视化分析&#xff1a;图书类型销量</div><div style"display: flex; margin: 10px 0"&g…

autosar学习笔记 之SecOC

SecOC 接下来SecOC标准就更复杂一点,它不单单是做了通讯校验。 SecOC是基于对称密钥加密的一套机制,需要对ECU间的通讯作身份认证处理,来更好的防止伪装攻击,谈起对称或非对称加密,就会涉及到密钥的存储和Mac值的计算。 因此SECOC机制对于密钥的硬件存储,也有一定的要求…

从零学习Hession RPC

为什么学习Hessian RPC&#xff1f; 存粹的RPC&#xff0c;只解决PRC的四个核心问题&#xff08;1.网络通信2.协议 3.序列化 4.代理&#xff09;Java写的HessianRPC落伍了&#xff0c;但是它的序列化方式还保存着&#xff0c;被Dubbo(Hessian Lite)使用。 被落伍&#xff0c;只…

K8S图像化工具rancher

Rancher是一个开源的企业级多集群的k8s管理平台 Rancher和k8s的区别 都是为了容器的调度和编排系统&#xff0c;但是rancher不仅能够调度&#xff0c;还能挂历k8s集群&#xff0c;自带监控&#xff08;普罗米修斯&#xff09;&#xff0c;你哪怕不知带k8s是什么&#xff0c;一样…

如何在Kali系统配置启动SSH并结合内网穿透实现远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

【Kafka】Kafka安装:Linux本地和Docker

目录 Linux本地安装kafkajava环境配置Zookeeper的安装配置Kafka的安装与配置生产与消费 Docker安装kafkaZookeeper安装Kafka安装 Linux本地安装kafka java环境配置 1、上传jdk-8u261-linux-x64.rpm到服务器并安装&#xff1a; rpm -ivh jdk-8u261-linux-x64.rpm2、配置环境变…

抖音跳转微信公众号是怎么实现的丨数灵通

抖音是一款非常流行的社交媒体应用程序&#xff0c;用户可以在其中分享短视频和互动内容。许多用户希望通过抖音来引流到他们的微信公众号&#xff0c;以扩大影响力并吸引更多粉丝。以下是一些关于如何在抖音上跳转到微信公众号的科普信息&#xff1a; 1.信息流广告&#xff1a…