ฉันจะจัดการ setters ในฟิลด์ที่ไม่เปลี่ยนรูปแบบได้อย่างไร


25

ฉันมีชั้นเรียนสองreadonly intสาขา พวกเขาถูกเปิดเผยเป็นคุณสมบัติ:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

อย่างไรก็ตามนั่นหมายความว่าต่อไปนี้เป็นรหัสทางกฎหมายที่สมบูรณ์แบบ:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

ข้อบกพร่องที่มาจากการสันนิษฐานว่าจะใช้งานได้แน่นอนจะร้ายกาจ แน่นอนว่านักพัฒนาที่เข้าใจผิดควรอ่านเอกสาร แต่นั่นไม่ได้เปลี่ยนความจริงที่ว่าไม่มีข้อผิดพลาดในการคอมไพล์หรือรันไทม์เตือนเขาเกี่ยวกับปัญหา

ควรเปลี่ยนThingคลาสอย่างไรเพื่อให้ devs มีโอกาสทำผิดพลาดน้อยกว่า

โยนข้อยกเว้นหรือไม่ ใช้วิธี getter แทนคุณสมบัติหรือไม่



1
ขอบคุณ @gnat ที่โพสต์ (ทั้งคำถามและคำตอบ) Interfacesดูเหมือนว่าจะพูดคุยเกี่ยวกับการเชื่อมต่อในขณะที่ทุนฉัน ฉันไม่แน่ใจว่าเป็นสิ่งที่ฉันทำ
kdbanman

6
@gnat นั่นเป็นคำแนะนำที่น่ากลัว อินเทอร์เฟซนำเสนอวิธีที่ยอดเยี่ยมในการให้บริการ VOs / DTO ที่ไม่เปลี่ยนรูปแบบสาธารณะซึ่งยังง่ายต่อการทดสอบ
David Arno

4
@gnat ฉันทำและคำถามไม่สมเหตุสมผลเนื่องจาก OP คิดว่าอินเตอร์เฟสจะทำลายความไม่สามารถเปลี่ยนแปลงได้ซึ่งเป็นเรื่องไร้สาระ
David Arno

1
@gnat คำถามนั้นเกี่ยวกับการประกาศอินเตอร์เฟสสำหรับ DTOs คำตอบที่ได้รับการโหวตสูงสุดคืออินเตอร์เฟสจะไม่เป็นอันตราย แต่อาจไม่จำเป็น
พอลเดรเปอร์

คำตอบ:


107

ทำไมต้องทำให้รหัสนั้นถูกกฎหมาย?
เอาออกset { }ถ้ามันไม่ทำอะไรเลย
นี่คือวิธีที่คุณกำหนดคุณสมบัติสาธารณะแบบอ่านอย่างเดียว:

public int Foo { get { return _foo; } }

3
ฉันไม่คิดว่ามันถูกกฎหมายด้วยเหตุผลบางอย่าง แต่ดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์แบบ! สมบูรณ์แบบขอบคุณ
kdbanman

1
@kdbanman อาจมีบางอย่างที่เกี่ยวข้องกับสิ่งที่คุณเห็นว่า IDE ทำในจุดหนึ่ง - อาจเป็นความสมบูรณ์อัตโนมัติ
Panzercrisis

2
@kdbanman; สิ่งที่ไม่ถูกกฎหมาย (ไม่เกิน C # 5) คือpublic int Foo { get; }(คุณสมบัติอัตโนมัติมีเพียงผู้ทะเยอทะยาน) แต่public int Foo { get; private set; }เป็น
Laurent LA RIZZA

@ LaurentLARIZZA คุณได้เขย่าความจำของฉันแล้ว นั่นคือสิ่งที่ทำให้ฉันสับสน
kdbanman

45

ด้วย C # 5 และก่อนหน้านี้เราต้องเผชิญกับสองตัวเลือกสำหรับฟิลด์ที่ไม่เปลี่ยนรูปที่เปิดเผยผ่านทะเยอทะยาน:

  1. สร้างตัวแปรสำรองแบบอ่านอย่างเดียวและส่งคืนผ่านทาง getter แบบแมนนวล ตัวเลือกนี้มีความปลอดภัย (หนึ่งจะต้องลบอย่างชัดเจนreadonlyเพื่อทำลาย immutability มันสร้างรหัสจานหม้อไอน้ำจำนวนมากแม้ว่า
  2. ใช้คุณสมบัติอัตโนมัติพร้อมตัวตั้งค่าส่วนตัว สิ่งนี้จะสร้างรหัสที่ง่ายขึ้น แต่จะง่ายกว่าที่จะทำลายความไม่เปลี่ยนแปลง

แม้ว่าด้วย C # 6 (มีให้บริการใน VS2015 ซึ่งเปิดตัวเมื่อวาน) ตอนนี้เราได้สิ่งที่ดีที่สุดทั้งสองโลก: คุณสมบัติอัตโนมัติแบบอ่านอย่างเดียว สิ่งนี้ทำให้เราสามารถทำให้คลาสของ OP ง่ายขึ้นเพื่อ:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

Foo = fooและBar = barเส้นเท่านั้นที่ถูกต้องในคอนสตรัค (ซึ่งประสบความสำเร็จในความต้องการอ่านอย่างเดียวที่แข็งแกร่ง) และเขตการสนับสนุนเป็นนัยมากกว่าที่มีการกำหนดไว้อย่างชัดเจน (ซึ่งประสบความสำเร็จในรหัสง่าย)


2
และผู้สร้างหลักสามารถทำให้สิ่งนี้ง่ายขึ้นยิ่งขึ้นโดยกำจัดค่าใช้จ่ายทางวากยสัมพันธ์ของตัวสร้าง
เซบาสเตียนเรดล

3
@SebastianRedl เศร้าก่อสร้างงานหลักที่ถูกถอดออกจาก C # 6 สุดท้ายปล่อย
Dan Lyons

1
@DanLyons ประณามฉันรอคอยที่จะใช้สิ่งนี้ตลอด codebase ของเรา
เซบาสเตียนเรดล

12

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

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}

1
" public int Foo { get; private set; }" ชั้นเรียนนั้นไม่เปลี่ยนรูปอีกต่อไปเพราะมันสามารถเปลี่ยนแปลงตัวเองได้ ขอบคุณ แต่!
kdbanman

4
คลาสที่อธิบายนั้นไม่เปลี่ยนรูปเนื่องจากไม่มีการเปลี่ยนแปลง
David Arno

1
ชั้นเรียนจริงของฉันซับซ้อนกว่าเล็กน้อยที่MWEฉันให้ไว้ ฉันผ่านไปไม่ได้จริงพร้อมความปลอดภัยด้ายและการค้ำประกันภายในระดับ
kdbanman

4
@kdbanman และประเด็นของคุณคืออะไร? หากชั้นเรียนของคุณเขียนไปที่ผู้ตั้งค่าส่วนตัวระหว่างการก่อสร้างเท่านั้น readonlyคำหลักเป็นเพียงโพกะโยเกคือมันป้องกันการเกิดการเรียนหมด immutablity ในอนาคต มันไม่จำเป็นที่จะต้องทำให้ไม่สามารถบรรลุผลได้แม้ว่า
David Arno

3
คำที่น่าสนใจฉันต้อง google it - poka-yoke เป็นคำภาษาญี่ปุ่นที่หมายถึง "การพิสูจน์อักษรผิดพลาด" คุณถูก! นั่นคือสิ่งที่ฉันทำ
kdbanman

6

สองทางแก้ไข

ง่าย:

อย่ารวมตัวตั้งค่าตามที่ดาวิดระบุไว้สำหรับวัตถุที่อ่านไม่ได้อย่างเดียว

อีกวิธีหนึ่งคือ:

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

pseudocode

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

โรงงานสาธารณะ

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

2
setXYZ เป็นชื่อที่น่ากลัวสำหรับวิธีการของโรงงานพิจารณาด้วย XYZ หรือสิ่งที่คล้าย
Ben Voigt

ขอบคุณอัปเดตคำตอบของฉันเพื่อสะท้อนความคิดเห็นที่เป็นประโยชน์ของคุณ :-)
Matt Smithies

คำถามกำลังถามโดยเฉพาะเกี่ยวกับตัวตั้งค่าคุณสมบัติไม่ใช่วิธีตัวตั้งค่า
user253751

จริง แต่ OP กังวลเกี่ยวกับสถานะที่ไม่แน่นอนจากนักพัฒนาในอนาคตที่เป็นไปได้ ตัวแปรที่ใช้คำหลักส่วนตัวแบบอ่านอย่างเดียวหรือส่วนตัวสุดท้ายควรเป็นเอกสารด้วยตนเองนั่นไม่ใช่ความผิด devs ดั้งเดิมหากไม่เข้าใจ การทำความเข้าใจวิธีการต่าง ๆ เพื่อเปลี่ยนสถานะเป็นกุญแจสำคัญ ฉันได้เพิ่มวิธีการอื่นซึ่งเป็นประโยชน์มาก มีหลายสิ่งในโลกโค้ดที่ทำให้เกิดความหวาดระแวงมากเกินไป vars ส่วนตัวที่อ่านอย่างเดียวไม่ควรเป็นหนึ่งในนั้น
Matt Smithies
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.