Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

คุณลักษณะของภาษา 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

Filename: src/lib.rs
pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}
Listing 18-1: struct AveragedCollection ที่รักษา list ของ integer และค่าเฉลี่ยของ item ใน collection

struct ถูกทำเครื่องหมาย pub เพื่อให้โค้ดอื่นใช้มันได้ แต่ field ภายใน struct ยัง private สิ่งนี้สำคัญในกรณีนี้เพราะเราต้องการรับ ประกันว่าเมื่อใดก็ตามที่ค่าถูกเพิ่มหรือลบจาก list ค่าเฉลี่ยก็ถูก อัพเดทด้วย เราทำสิ่งนี้โดย implement เมธอด add, remove และ average บน struct ดังที่แสดงใน Listing 18-2

Filename: src/lib.rs
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;
    }
}
Listing 18-2: Implementation ของเมธอด public 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 ทำงานยังไง