วิธีการส่วนตัวควรใช้เส้นทางสาธารณะในการเข้าถึงข้อมูลส่วนตัวเมื่อใด


11

วิธีการส่วนตัวควรใช้เส้นทางสาธารณะในการเข้าถึงข้อมูลส่วนตัวเมื่อใด ตัวอย่างเช่นถ้าฉันมีคลาส 'ตัวทวีคูณ' ที่ไม่เปลี่ยนรูปแบบ (มีการประดิษฐ์มาบ้างฉันรู้):

class Multiplier {
public:
    Multiplier(int a, int b) : a(a), b(b) { }
    int getA() const { return a; }
    int getB() const { return b; }
    int getProduct() const { /* ??? */ }
private:
    int a, b;
};

มีสองวิธีที่ฉันสามารถใช้getProduct:

    int getProduct() const { return a * b; }

หรือ

    int getProduct() const { return getA() * getB(); }

เพราะความตั้งใจที่นี่คือการใช้ค่าของaคือเพื่อให้ได้ aใช้getA()ในการใช้งานgetProduct()ดูเหมือนว่าสะอาดสำหรับฉัน ฉันต้องการหลีกเลี่ยงการใช้aเว้นแต่ฉันจะต้องแก้ไขมัน ความกังวลของฉันเป็นที่ฉันไม่ได้มักจะเห็นโค้ดที่เขียนด้วยวิธีนี้ในประสบการณ์ของผมจะมีการดำเนินงานที่พบได้บ่อยกว่าa * bgetA() * getB()

วิธีการส่วนตัวควรใช้วิธีสาธารณะเมื่อพวกเขาสามารถเข้าถึงบางสิ่งบางอย่างได้โดยตรงหรือไม่

คำตอบ:


7

มันขึ้นอยู่กับความหมายที่แท้จริงของa, bและgetProduct.

วัตถุประสงค์ของ getters คือสามารถเปลี่ยนการใช้งานจริงในขณะที่รักษาอินเทอร์เฟซของวัตถุให้เหมือนเดิม ตัวอย่างเช่นถ้าหนึ่งวันgetAกลายreturn a + 1;เป็นการเปลี่ยนแปลงจะแปลเป็นผู้ทะเยอทะยาน

กรณีสถานการณ์จริงบางครั้งซับซ้อนกว่าเขตข้อมูลสำรองคงที่ที่กำหนดผ่านตัวสร้างที่เชื่อมโยงกับทะเยอทะยาน ตัวอย่างเช่นค่าของฟิลด์อาจคำนวณหรือโหลดจากฐานข้อมูลในรหัสต้นฉบับ ในเวอร์ชันถัดไปอาจมีการเพิ่มแคชเพื่อเพิ่มประสิทธิภาพ หากgetProductใช้เวอร์ชันที่คำนวณต่อไปจะไม่ได้รับประโยชน์จากการแคช (หรือผู้ดูแลจะทำการเปลี่ยนแปลงเดียวกันสองครั้ง)

หากgetProductใช้aกับbโดยตรงและเหมาะสมที่สุดให้ใช้งาน มิฉะนั้นใช้ getters เพื่อป้องกันปัญหาการบำรุงรักษาในภายหลัง

ตัวอย่างที่หนึ่งจะใช้ getters:

class Product {
public:
    Product(ProductId id) : {
        price = Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPrice() {
        return price;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate); // ← Using a getter instead of a field.
    }
private:
    Money price;
}

ในขณะที่ผู้ทะเยอทะยานไม่มีตรรกะทางธุรกิจใด ๆ มันก็ไม่ได้ยกเว้นว่าตรรกะในตัวสร้างจะถูกโยกย้ายไปยังผู้ทะเยอทะยานเพื่อหลีกเลี่ยงการทำงานของฐานข้อมูลเมื่อเริ่มต้นวัตถุ:

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
}

ต่อมาอาจมีการเพิ่มแคช (ใน C #, จะใช้Lazy<T>, ทำให้รหัสสั้นและง่าย; ฉันไม่รู้ว่ามีเทียบเท่าใน C ++):

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        if (priceCache == NULL) {
            priceCache = Money.fromCents(
                data.findProductById(id).price,
                environment.currentCurrency
            )

        return priceCache;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
    Money priceCache;
}

การเปลี่ยนแปลงทั้งสองมุ่งเน้นไปที่ทะเยอทะยานและเขตข้อมูลสำรองรหัสที่เหลือจะไม่ได้รับผลกระทบ หากฉันใช้สนามแทนผู้ทะเยอทะยานgetPriceWithRebateฉันก็ต้องสะท้อนการเปลี่ยนแปลงที่นั่นเช่นกัน

ตัวอย่างที่หนึ่งอาจใช้เขตข้อมูลส่วนตัว:

class Product {
public:
    Product(ProductId id) : id(id) { }
    ProductId getId() const { return id; }
    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price, // ← Accessing `id` directly.
            environment.currentCurrency
        )
    }
private:
    const ProductId id;
}

ทะเยอทะยานตรงไปตรงมา: มันเป็นตัวแทนโดยตรงของสนามคงที่ (คล้ายกับ C # 's readonly) ซึ่งไม่คาดว่าจะมีการเปลี่ยนแปลงในอนาคต: โอกาสเป็น ID getter จะไม่กลายเป็นค่าที่คำนวณได้ เพื่อให้ง่ายและเข้าถึงฟิลด์โดยตรง

ข้อดีอีกอย่างคือgetIdอาจถูกลบออกในอนาคตหากปรากฏว่าไม่ได้ใช้งานภายนอก (เช่นในรหัสก่อนหน้า)


ฉันไม่สามารถให้ +1 คุณได้เนื่องจากตัวอย่างของคุณที่จะใช้ฟิลด์ส่วนตัวไม่ใช่ IMHO ส่วนใหญ่เป็นเพราะคุณได้ประกาศconst: ฉันถือว่านั่นหมายความว่าคอมไพเลอร์จะทำการgetIdโทรแบบอินไลน์และช่วยให้คุณสามารถเปลี่ยนแปลงได้ในทิศทางใดทิศทางหนึ่ง (ไม่เช่นนั้นฉันเห็นด้วยกับเหตุผลของคุณอย่างเต็มที่ในการใช้ getters) และในภาษาที่มีไวยากรณ์ของคุณสมบัติก็มีเหตุผลน้อยกว่าที่จะไม่ใช้คุณสมบัติแทนที่จะใช้เขตข้อมูลสำรองโดยตรง
Mark Hurd

1

โดยทั่วไปแล้วคุณจะใช้ตัวแปรโดยตรง คุณคาดว่าจะเปลี่ยนสมาชิกทั้งหมดเมื่อเปลี่ยนการใช้งานของชั้นเรียน การไม่ใช้ตัวแปรโดยตรงทำให้ยากต่อการแยกรหัสที่ถูกต้องขึ้นอยู่กับพวกเขาและทำให้สมาชิกอ่านยากขึ้น

สิ่งนี้แตกต่างกันหากผู้ใช้ใช้ตรรกะจริงในกรณีนั้นขึ้นอยู่กับว่าคุณต้องใช้ตรรกะของพวกเขาหรือไม่


1

ฉันจะบอกว่าใช้วิธีการสาธารณะจะดีกว่าหากไม่ได้สำหรับเหตุผลอื่นใด แต่เพื่อให้สอดคล้องกับแห้ง

ฉันรู้ว่าในกรณีของคุณคุณมีเขตข้อมูลสำรองสำหรับ accessors ของคุณ แต่คุณอาจมีตรรกะบางอย่างเช่นรหัสการโหลดขี้เกียจซึ่งคุณต้องเรียกใช้ก่อนที่คุณจะใช้ตัวแปรครั้งแรก ดังนั้นคุณต้องการโทรหาผู้เข้าถึงของคุณแทนการอ้างอิงโดยตรงไปยังเขตข้อมูลของคุณ แม้ว่าคุณจะไม่ได้รับสิ่งนี้ในกรณีนี้ แต่ก็เหมาะสมที่จะทำตามแบบแผนเดียว ด้วยวิธีนี้ถ้าคุณเปลี่ยนแปลงตรรกะของคุณคุณจะต้องเปลี่ยนมันในที่เดียว


0

สำหรับชั้นเรียนขนาดเล็กความเรียบง่ายนี้จะชนะ ฉันจะใช้ * b

สำหรับสิ่งที่ซับซ้อนกว่านี้ฉันจะพิจารณาใช้ getA () * getB () อย่างยิ่งถ้าฉันต้องการแยกส่วนติดต่อ "ขั้นต่ำ" จากฟังก์ชั่นอื่น ๆ ทั้งหมดใน API สาธารณะแบบเต็ม ตัวอย่างที่ดีคือ std :: string ใน C ++ มันมีฟังก์ชั่นสมาชิก 103 คน แต่มีเพียง 32 ฟังก์ชันเท่านั้นที่จำเป็นต้องเข้าถึงสมาชิกส่วนตัว หากคุณมีคลาสที่ซับซ้อนการบังคับใช้ฟังก์ชัน "ที่ไม่ใช่คอร์" ทั้งหมดเพื่อให้ผ่าน "คอร์ API" อย่างต่อเนื่องอาจทำให้การใช้งานง่ายขึ้นมากในการทดสอบการดีบักและการรีแฟคเตอร์


1
หากคุณมีชั้นเรียนที่ซับซ้อนคุณควรถูกบังคับให้แก้ไขไม่ต้องรัดมัน
DeadMG

ตกลง ฉันน่าจะเลือกตัวอย่างด้วยฟังก์ชัน 20-30 อัน
Ixrec

1
"103 ฟังก์ชั่น" เป็นบิตของปลาเฮอริ่งแดง เมธอดที่โอเวอร์โหลดควรถูกนับหนึ่งครั้งในแง่ถ้าความซับซ้อนของอินเตอร์เฟส
Avner Shahar-Kashtan

ฉันไม่เห็นด้วยอย่างสมบูรณ์ เกินพิกัดที่แตกต่างกันสามารถมีความหมายที่แตกต่างกันและอินเตอร์เฟซที่แตกต่างกัน
DeadMG

แม้ตัวอย่างนี้ "เล็ก" getA() * getB()ก็ดีกว่าในระยะกลางและระยะยาว
Mark Hurd
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.