午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

[Rust開發(fā)]用可視化案例講Rust編程4.用泛型和特性實現(xiàn)自適配shapefile的讀取

 godxiasad 2024-01-18 發(fā)布于北京

想到就發(fā)了,反正大家也不看

本節(jié)已經(jīng)涉及Rust學習曲線上的一個大坑:泛型和特性了,屬于語言的深水區(qū),如果初學者,建議看一眼知道有這個功能即可。

如果我們立足于功能實現(xiàn),那么做到像上一節(jié)那樣就可以了,從原理上來說,每個函數(shù)滿足唯一的功能,是一種好的設計,軟件工程里面“高內(nèi)聚低耦合”是有利于系統(tǒng)的獨立性的。

但是在調(diào)用的時候,就需要有一點的心智了,例如我們要讀取點線面三種不同的shapefile,就要記住三種不同的函數(shù)名和三種不同的繪圖要素構(gòu)建程序,那也太麻煩了,有沒有一種方法,讓我們調(diào)用一種方法,根據(jù)不同的參數(shù)類型自動適配不同的處理邏輯呢?

如果你是學Python/Java/JavaScript這樣的高級語言的同學,你肯定已經(jīng)有了自己的實現(xiàn)方法,例如Python/Javascript作為動態(tài)類型的語言,可以只寫一個入口函數(shù),然后根據(jù)函數(shù)的輸入?yún)?shù)自動去選擇不同的分支來處理。

而Java也有同名方法覆蓋,利用不同的函數(shù)簽名(參數(shù)類型)來解決用同名函數(shù)實現(xiàn)不同的邏輯流程。

而C++則是可以用泛型和函數(shù)重載來實現(xiàn)這個功能。

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

  • 首先,Rust不支持函數(shù)簽名的類型推斷。所有函數(shù)簽名的類型必須寫清楚,這就杜絕了我們用動態(tài)類型的語言那樣根據(jù)輸入的參數(shù)來匹配。

  • 第二,Rust也不(直接)支持函數(shù)重載,為什么是不直接支持呢,因為可以通過泛型和特性來實現(xiàn)函數(shù)重載,也就是我們今天要做的內(nèi)容。

老規(guī)矩,先看結(jié)果:

用同名的一個方法,實現(xiàn)對于三種不同的shapefile讀取,并且獲取三種不同的返回類型值,那么這是這么實現(xiàn)的呢?

先看代碼:

架構(gòu)定義

  • 首先定義了一個結(jié)構(gòu)體,這個空結(jié)構(gòu)體的作用類似于Java里面的class。

  • 然后定義了一個特性(trait),特性這個東西,在Rust中,類似于Java里面的接口,或者C++里面的虛函數(shù),但是與接口和虛函數(shù)不同的時候,特性可以直接在里面寫實現(xiàn),也可以留空,如果你是學Java的,你把下面的代碼理解為定義了一個接口,并且定義了一個實現(xiàn)接口的工廠方法即可。

  • 在這里的特性中,我們定義一個泛型T,然后定義了一個方法,就叫做read_shp,輸入產(chǎn)生就是一個shapefile的路徑,返回值是一個T ,這個T就是Rust中的泛型,即代表一切可能的類型。

    • 如果是沒有接觸過泛型的同學,可能會問,他既然代表一切類型,那么你不是說了Rust是類型嚴格的語言么?那系統(tǒng)編譯器怎么知道,這個T到底是哪種類型呢?繼續(xù)往下看

pub struct shp;
pub trait ReadShapfile<T> {
fn read_shp(shp_path:&str) -> T;
}

然后我們開始寫針對不同類型的shapefile的實現(xiàn),實現(xiàn)如下:

//實現(xiàn)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
}
}

//實現(xiàn)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
}
}
//實現(xiàn)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 結(jié)構(gòu)名 這個語法,代表了具體的實現(xiàn),而里面那個 <返回值> 部分,就是我們定義的泛型T,注意,T只是一個代稱,習慣性用大寫字母,你用ABCDEFG,或者abcdefg都是可以的。

  • 然后在這個實現(xiàn)里面,去寫具體方法的read_shp的實現(xiàn),寫完之后,就可以通過在調(diào)用的時候,再指定返回類型,來適配具體調(diào)用哪個方法了。

看到這里,可能有同學又有問題了:

上一節(jié),直接寫多個方法,多實用啊,讀polygon就是polygon方法,讀point就是point方法,干凈整潔又高效,干嘛搞這么麻煩?

實際上兩種方式,都是可以的,每個方法用獨立名稱進行調(diào)用,實際上是一種比較傳統(tǒng),但是很高效的方式(C語言里面寫操作系統(tǒng)內(nèi)核都是這樣寫的)。

而后面這種,則是現(xiàn)代編程架構(gòu)里面推薦的,用同名函數(shù),來減少調(diào)用者的心智負擔,反正都是讀取shapefile,我干嘛還要記那么多個函數(shù)名???為什么不能用一個函數(shù)就全部解決了呢?

所以在軟件開發(fā)中,同名函數(shù),通過定義不同的返回值,或者輸入值來區(qū)分的方式,叫做多態(tài),而在Rust里面,多態(tài)又可以分為靜態(tài)分發(fā):即靜態(tài)多態(tài)模式或者單一態(tài)模式;以及動態(tài)分發(fā):即動態(tài)多態(tài)模式。

這種所謂的靜態(tài)動態(tài),在Rust中,是針對編譯而言的,靜態(tài)即代碼和方法,在被編譯的時候,就已經(jīng)決定了編譯的結(jié)果,你的輸入、輸出、處理流程等,都在編譯的時候被固定下來了。

這種也是函數(shù)式編程的核心思想:函數(shù)式編程強調(diào):不可變性和純函數(shù),這意味著函數(shù)的輸出只取決于它的輸入,而不依賴于外部狀態(tài)

動態(tài)則相反,在編譯的時候,系統(tǒng)并不知道要輸入的參數(shù)和輸出的結(jié)果具體是什么類型,只有在運行的時候,根據(jù)輸入的數(shù)據(jù)情況才知道。

一般來說,動態(tài)語言中的動,就是這種,通常解釋性的語言,都是動態(tài)的,例如Python/Javascript等。

當然,Rust也是支持動態(tài)編譯的,但是在使用動態(tài)編譯的時候,需要有一系列的手續(xù),這個我們以后再說。

從兩種編譯模式來看,靜態(tài)模式的開銷小,安全性高,但是靈活性比較差;反之,動態(tài)模式靈活性好,但是開銷大,安全性和穩(wěn)定性較差。

具體在開發(fā)過程中,用哪種模式,就仁者見仁智者見智了。

最后我們來畫一個UML圖,就很容易看出來這個架構(gòu)的實現(xiàn)思路了:

    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多