คุณลักษณะของภาษา Object-Oriented
ไม่มีฉันทามติใน community programming เกี่ยวกับว่าฟีเจอร์อะไรที่ ภาษาต้องมีเพื่อพิจารณาว่าเป็น object oriented Rust ได้รับอิทธิพล จาก paradigm programming หลายตัว รวม OOP — ตัวอย่างเช่น เราสำรวจ ฟีเจอร์ที่มาจาก functional programming ในบทที่ 13 อาจกล่าวได้ว่า ภาษา OOP แชร์ลักษณะทั่วไปบางอย่าง — กล่าวคือ object, encapsulation และ inheritance มาดูว่าแต่ละลักษณะเหล่านั้นหมายความอะไรและ Rust สนับสนุนมันไหม
Object บรรจุข้อมูลและพฤติกรรม
หนังสือ Design Patterns: Elements of Reusable Object-Oriented Software โดย Erich Gamma, Richard Helm, Ralph Johnson และ John Vlissides (Addison-Wesley, 1994) อ้างถึงในภาษาพูดว่า The Gang of Four คือ catalog ของ object-oriented design pattern มันนิยาม OOP ในวิธีนี้:
โปรแกรม object-oriented ประกอบด้วย object Object บรรจุทั้ง ข้อมูลและ procedure ที่ operate บนข้อมูลนั้น procedure ถูกเรียก โดยทั่วไปว่า method หรือ operation
โดยใช้นิยามนี้ Rust เป็น object oriented — struct และ enum มีข้อมูล
และ block impl ให้เมธอดบน struct และ enum แม้ struct และ enum
ที่มีเมธอดไม่ ถูกเรียก object พวกมันให้ functionality เดียวกัน
ตามนิยามของ Gang of Four ของ object
Encapsulation ที่ซ่อนรายละเอียด Implementation
แง่มุมอีกอย่างที่ associate ทั่วไปกับ OOP คือไอเดียของ encapsulation ซึ่งหมายความว่ารายละเอียด implementation ของ object ไม่เข้าถึงได้โดยโค้ดที่ใช้ object นั้น ดังนั้น วิธีเดียวที่ จะ interact กับ object คือผ่าน public API ของมัน — โค้ดที่ใช้ object ไม่ควรไปถึงภายในของ object และเปลี่ยนข้อมูลหรือพฤติกรรม โดยตรง นี่เปิดใช้ programmer ให้เปลี่ยนและ refactor ภายในของ object โดยไม่ต้องเปลี่ยนโค้ดที่ใช้ object
เราพูดถึงวิธีควบคุม encapsulation ในบทที่ 7 — เราใช้ keyword pub
เพื่อตัดสินว่าโมดูล, type, ฟังก์ชัน และเมธอดใดในโค้ดของเราควรเป็น
public และตามค่าเริ่มต้นทุกอย่างอื่นเป็น private ตัวอย่างเช่น เรา
นิยาม struct AveragedCollection ที่มี field บรรจุ vector ของค่า
i32 struct ยังมี field ที่บรรจุค่าเฉลี่ยของค่าใน vector ได้
หมายความว่าค่าเฉลี่ยไม่ต้องถูกคำนวณตามความต้องการเมื่อใครก็ตามต้อง
มัน อีกแง่หนึ่ง AveragedCollection จะ cache ค่าเฉลี่ยที่คำนวณ
ให้เรา Listing 18-1 มีนิยามของ struct AveragedCollection
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
AveragedCollection ที่รักษา list ของ integer และค่าเฉลี่ยของ item ใน collectionstruct ถูกทำเครื่องหมาย pub เพื่อให้โค้ดอื่นใช้มันได้ แต่ field
ภายใน struct ยัง private สิ่งนี้สำคัญในกรณีนี้เพราะเราต้องการรับ
ประกันว่าเมื่อใดก็ตามที่ค่าถูกเพิ่มหรือลบจาก list ค่าเฉลี่ยก็ถูก
อัพเดทด้วย เราทำสิ่งนี้โดย implement เมธอด add, remove และ
average บน struct ดังที่แสดงใน Listing 18-2
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
impl AveragedCollection {
pub fn add(&mut self, value: i32) {
self.list.push(value);
self.update_average();
}
pub fn remove(&mut self) -> Option<i32> {
let result = self.list.pop();
match result {
Some(value) => {
self.update_average();
Some(value)
}
None => None,
}
}
pub fn average(&self) -> f64 {
self.average
}
fn update_average(&mut self) {
let total: i32 = self.list.iter().sum();
self.average = total as f64 / self.list.len() as f64;
}
}
add, remove และ average บน AveragedCollectionเมธอด public add, remove และ average เป็นวิธีเดียวที่จะเข้า
ถึงหรือแก้ข้อมูลใน instance ของ AveragedCollection เมื่อ item
ถูกเพิ่มให้ list โดยใช้เมธอด add หรือลบโดยใช้เมธอด remove
implementation ของแต่ละเรียกเมธอด private update_average ที่
จัดการการอัพเดท field average ด้วย
เราเหลือ field list และ average private เพื่อให้ไม่มีวิธีให้
โค้ดภายนอกเพิ่มหรือลบ item เข้าหรือออกจาก field list โดยตรง —
มิฉะนั้น field average อาจกลายเป็น out of sync เมื่อ list
เปลี่ยน เมธอด average return ค่าใน field average อนุญาตให้โค้ด
ภายนอกอ่าน average แต่ไม่แก้มัน
เพราะเรา encapsulate รายละเอียด implementation ของ struct
AveragedCollection เราเปลี่ยนแง่มุม เช่นโครงสร้างข้อมูล ในอนาคต
ได้ง่าย ตัวอย่างเช่น เราใช้ HashSet<i32> แทน Vec<i32> สำหรับ
field list ได้ ตราบใดที่ signature ของเมธอด public add,
remove และ average ยังเหมือนเดิม โค้ดที่ใช้ AveragedCollection
ไม่ต้องเปลี่ยน ถ้าเราทำ list เป็น public แทน นี่จะไม่จำเป็นเป็น
กรณี — HashSet<i32> และ Vec<i32> มีเมธอดต่างกันสำหรับการเพิ่ม
และลบ item ดังนั้นโค้ดภายนอกอาจต้องเปลี่ยนถ้ามันแก้ list โดยตรง
ถ้า encapsulation เป็นแง่มุมที่ต้องการสำหรับภาษาที่จะพิจารณาเป็น
object oriented Rust ตรงข้อกำหนดนั้น ตัวเลือกในการใช้ pub หรือ
ไม่สำหรับส่วนต่างกันของโค้ดเปิดใช้ encapsulation ของรายละเอียด
implementation
Inheritance ในฐานะระบบ Type และในฐานะการแชร์โค้ด
Inheritance คือกลไกที่ object สืบทอด element จากนิยามของ object อื่น ดังนั้นได้ข้อมูลและพฤติกรรมของ object parent โดยคุณไม่ต้อง นิยามพวกมันอีก
ถ้าภาษาต้องมี inheritance เพื่อเป็น object oriented Rust ก็ไม่ใช่ ภาษาเช่นนั้น ไม่มีวิธีนิยาม struct ที่สืบทอด field และ implementation เมธอดของ struct parent โดยไม่ใช้ macro
อย่างไรก็ตาม ถ้าคุณคุ้นเคยกับการมี inheritance ใน toolbox programming ของคุณ คุณใช้วิธีแก้อื่นใน Rust ขึ้นกับเหตุผลของคุณ สำหรับการเอื้อมไป inheritance ที่แรก
คุณจะเลือก inheritance ด้วยเหตุผลหลักสองอย่าง อันหนึ่งคือสำหรับการ
ใช้โค้ดซ้ำ — คุณ implement พฤติกรรมเฉพาะสำหรับหนึ่ง type และ
inheritance เปิดใช้ให้คุณใช้ implementation นั้นซ้ำสำหรับอีก type คุณ
ทำสิ่งนี้ในแบบจำกัดในโค้ด Rust โดยใช้ implementation เมธอด trait
เริ่มต้น ซึ่งคุณเห็นใน Listing 10-14 เมื่อเราเพิ่ม implementation
เริ่มต้นของเมธอด summarize บน trait Summary ได้ type ใดที่
implement trait Summary จะมีเมธอด summarize ใช้ได้บนมันโดย
ไม่มีโค้ดเพิ่ม นี่คล้ายกับ class parent ที่มี implementation ของ
เมธอดและ class child ที่สืบทอดก็มี implementation ของเมธอด เรายัง
override implementation เริ่มต้นของเมธอด summarize เมื่อเรา
implement trait Summary ได้ ซึ่งคล้ายกับ class child ที่ override
implementation ของเมธอดที่สืบทอดจาก class parent
เหตุผลอื่นในการใช้ inheritance เกี่ยวกับระบบ type — เพื่อเปิดใช้ type child ที่จะถูกใช้ในที่เดียวกับ type parent นี่ยังเรียก polymorphism ซึ่งหมายความว่าคุณทดแทน object หลายตัวสำหรับกันและ กันที่ runtime ได้ถ้าพวกมันแชร์ลักษณะบางอย่าง
Polymorphism
สำหรับคนหลายคน polymorphism เป็น synonymous กับ inheritance แต่ มันจริง ๆ เป็นแนวคิดทั่วไปกว่าที่อ้างถึงโค้ดที่ทำงานกับข้อมูลของ หลาย type สำหรับ inheritance type เหล่านั้นโดยทั่วไปคือ subclass
Rust แทนใช้ generic เพื่อนามธรรมเหนือ type ต่างกันที่เป็นไปได้ และ trait bound เพื่อบังคับข้อจำกัดบนสิ่งที่ type เหล่านั้นต้อง ให้ นี่บางครั้งเรียก bounded parametric polymorphism
Rust เลือกชุดของ trade-off ต่างกันโดยไม่เสนอ inheritance Inheritance มักเสี่ยงในการแชร์โค้ดมากกว่าจำเป็น Subclass ไม่ควรแชร์ลักษณะทั้งหมด ของ class parent ของพวกมันเสมอ แต่จะทำเช่นนั้นด้วย inheritance นี่ ทำให้การออกแบบของโปรแกรมยืดหยุ่นน้อยลงได้ มันยังแนะนำความเป็นไปได้ ในการเรียกเมธอดบน subclass ที่ไม่สมเหตุสมผลหรือก่อให้เกิด error เพราะเมธอดไม่ใช้กับ subclass นอกจากนี้ บางภาษาจะอนุญาตเพียง single inheritance (หมายความว่า subclass สืบทอดได้จากเพียงหนึ่ง class) จำกัดความยืดหยุ่นของการออกแบบของโปรแกรมเพิ่ม
ด้วยเหตุผลเหล่านี้ Rust ใช้แนวทางต่างของการใช้ trait object แทน inheritance เพื่อบรรลุ polymorphism ที่ runtime มาดูว่า trait object ทำงานยังไง