วิธีปฏิบัติที่ดีที่สุดที่ส่งคืนวัตถุแบบอ่านอย่างเดียว


11

ฉันมีคำถาม "แนวปฏิบัติที่ดีที่สุด" เกี่ยวกับ OOP ใน C # (แต่การเรียงลำดับจะใช้กับทุกภาษา)

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

class A
{
    // Note: List is just example, I am interested in objects in general.
    private List<string> _items = new List<string>() { "hello" }

    public List<string> Items
    {
        get
        {
            // Option A (not read-only), can be modified from outside:
            return _items;

            // Option B (sort-of read-only):
            return new List<string>( _items );

            // Option C, must change return type to ReadOnlyCollection<string>
            return new ReadOnlyCollection<string>( _items );
        }
    }
}

เห็นได้ชัดว่าแนวทางที่ดีที่สุดคือ "ตัวเลือก C" แต่มีวัตถุเพียงไม่กี่ตัวที่มีตัวแปร ReadOnly (และแน่นอนว่าไม่มีคลาสที่ผู้ใช้กำหนดเอง)

หากคุณเป็นผู้ใช้งานคลาส A คุณคาดหวังว่าจะมีการเปลี่ยนแปลงหรือไม่

List<string> someList = ( new A() ).Items;

เผยแพร่ไปยังวัตถุ (A) เดิมหรือไม่ หรือมันโอเคที่จะส่งคืนโคลนนิ่งที่เขียนในคอมเม้นท์ / เอกสาร? ฉันคิดว่าวิธีการโคลนนี้อาจนำไปสู่ข้อบกพร่องที่ยากต่อการติดตาม

ฉันจำได้ว่าใน C ++ เราสามารถคืนค่าวัตถุ const และคุณสามารถเรียกวิธีการทำเครื่องหมายเป็น const ได้เท่านั้น ฉันเดาว่าไม่มีคุณสมบัติ / รูปแบบดังกล่าวใน C # หรือไม่ ถ้าไม่ทำไมพวกเขาจะไม่รวมมัน? ฉันเชื่อว่ามันถูกเรียกว่า const

แต่แล้วคำถามหลักของฉันอีกครั้งเกี่ยวกับ "สิ่งที่คุณคาดหวัง" หรือวิธีการจัดการตัวเลือก A vs B


2
ดูบทความนี้เกี่ยวกับตัวอย่างนี้ของคอลเลกชันที่ไม่เปลี่ยนรูปแบบใหม่สำหรับ. NET 4.5: blogs.msdn.com/b/bclteam/archive/2012/12/18/ ......คุณสามารถรับพวกเขาผ่าน NuGet ได้แล้ว
Kristof Claes

คำตอบ:


10

นอกจากคำตอบของ @ Jesse ซึ่งเป็นทางออกที่ดีที่สุดสำหรับคอลเลกชัน: แนวคิดทั่วไปคือการออกแบบส่วนต่อประสานสาธารณะของคุณในแบบที่มันจะกลับมาเท่านั้น

  • ประเภทค่า (เช่น int, double, struct)

  • วัตถุที่ไม่เปลี่ยนรูป (เช่น "สตริง" หรือคลาสที่ผู้ใช้กำหนดซึ่งมีวิธีการอ่านอย่างเดียวเท่านั้นเช่นเดียวกับคลาส A ของคุณ)

  • (เฉพาะในกรณีที่คุณต้อง): คัดลอกวัตถุเช่น "ตัวเลือก B" ของคุณแสดงให้เห็นถึง

IEnumerable<immutable_type_here> มีการเปลี่ยนแปลงไม่ได้ด้วยตัวเองดังนั้นนี่เป็นเพียงกรณีพิเศษของข้างต้น


19

IEnumerable<string>นอกจากนี้ยังทราบคุณควรจะเขียนโปรแกรมอินเตอร์เฟซและโดยและขนาดใหญ่สิ่งที่คุณต้องการจะกลับมาจากทรัพย์สินที่เป็น A List<string>เป็นอะไรที่ไม่เด็ดขาดเพราะโดยทั่วไปมักจะเปลี่ยนแปลงไม่ได้และฉันจะไปไกลจนพูดได้ว่าReadOnlyCollection<string>ในฐานะผู้ดำเนินการICollection<string>รู้สึกว่ามันละเมิดหลักการแทน Liskov


6
+1 การใช้ a IEnumerable<string>คือสิ่งที่เราต้องการที่นี่
Doc Brown เมื่อ

2
ในสุทธิ 4.5 IReadOnlyList<T>คุณยังสามารถใช้
svick

3
หมายเหตุสำหรับผู้มีส่วนได้เสียอื่น เมื่อพิจารณาตัวเข้าถึงคุณสมบัติ: ถ้าคุณเพียงแค่คืนค่า IEnumerable <string> แทน List <string> โดยการส่งคืน _list (+ การส่งแบบอ้อมไปยัง IEnumerable) ดังนั้นรายการนี้สามารถถูกส่งกลับไปยังรายการ <string> และเปลี่ยนแปลงและการเปลี่ยนแปลงจะ เผยแพร่สู่วัตถุหลัก คุณสามารถส่งคืนรายการใหม่ <string> (_list) (+ ส่งต่อโดยนัยไปยัง IEnumerable) ซึ่งจะป้องกันรายการภายใน ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้สามารถพบได้ที่นี่: stackoverflow.com/questions/4056013/…
NeverStopLearning

1
มันจะดีกว่าที่จะต้องให้ผู้รับใด ๆ ของคอลเลกชันที่ต้องการเข้าถึงโดยดัชนีจะต้องเตรียมที่จะคัดลอกข้อมูลลงในประเภทที่อำนวยความสะดวกนั้นหรือมันจะดีกว่าที่จะต้องให้รหัสกลับคอลเลกชันจัดหามันในรูปแบบที่ สามารถเข้าถึงได้โดยดัชนี? หากผู้จัดหามีแนวโน้มที่จะมีในรูปแบบที่ไม่อำนวยความสะดวกในการเข้าถึงโดยใช้ดัชนีฉันจะพิจารณาว่ามูลค่าของการปลดปล่อยผู้รับจากความต้องการในการทำสำเนาซ้ำซ้อนของตัวเองจะเกินกว่ามูลค่าของการปลดปล่อยผู้จัดหา แบบฟอร์มการเข้าถึงแบบสุ่ม (เช่นแบบอ่านอย่างเดียวIList<T>)
supercat

2
สิ่งหนึ่งที่ฉันไม่ชอบเกี่ยวกับ IEnumerable คือคุณไม่เคยรู้ว่ามันทำงานเป็น coroutine หรือว่ามันเป็นเพียงวัตถุข้อมูล ถ้ามันทำงานเป็น coroutine มันอาจส่งผลให้เกิดข้อบกพร่องที่ลึกซึ้งดังนั้นนี่เป็นเรื่องดีที่จะรู้ในบางครั้ง นี่คือเหตุผลที่ฉันมักจะชอบ ReadOnlyCollection หรือ IReadOnlyList เป็นต้น
Steve Vermeulen

3

ฉันพบปัญหาที่คล้ายกันในขณะที่ด้านหลังและด้านล่างเป็นวิธีแก้ปัญหาของฉัน แต่ใช้งานได้จริง ๆ ถ้าคุณควบคุมไลบรารีด้วย:


เริ่มด้วยชั้นเรียนของคุณ A

public class A
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

จากนั้นรับอินเทอร์เฟซจากสิ่งนี้ด้วยการรับส่วนของคุณสมบัติ (และวิธีการอ่านใด ๆ ) เท่านั้นและมีการนำมาใช้

public interface IReadOnlyA
{
    public int N { get; }
}
public class A : IReadOnlyA
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

แน่นอนว่าเมื่อคุณต้องการใช้เวอร์ชันอ่านอย่างเดียวการส่งแบบง่ายก็เพียงพอแล้ว นี่เป็นเพียงสิ่งที่คุณแก้ปัญหา C จริงๆ แต่ฉันคิดว่าฉันเสนอวิธีที่ฉันทำได้


2
ปัญหาเกี่ยวกับเรื่องนี้คือมันเป็นไปได้ที่จะส่งมันกลับเป็นเวอร์ชันที่ไม่แน่นอน และการฝ่าฝืน LSP ทำให้รัฐสามารถสังเกตได้ด้วยวิธีการของคลาสพื้นฐาน (ในกรณีนี้) สามารถเปลี่ยนแปลงได้ด้วยวิธีการใหม่จากคลาสย่อย แต่อย่างน้อยก็มีเจตนาทำลายล้างแม้ว่ามันจะไม่บังคับใช้ก็ตาม
user470365

1

สำหรับทุกคนที่พบว่าคำถามนี้และยังคงเป็นที่สนใจในคำตอบ - ImmutableArray<T>ตอนนี้มีตัวเลือกอื่นซึ่งเป็นที่เปิดเผย นี่เป็นเพียงไม่กี่จุดที่ฉันคิดว่าดีกว่าIEnumerable<T>หรือReadOnlyCollection<T>:

  • มันระบุอย่างชัดเจนว่ามันไม่เปลี่ยนรูป (API ที่แสดงออก)
  • มันระบุไว้อย่างชัดเจนว่าการสะสมนั้นเกิดขึ้นแล้ว (ในคำอื่น ๆ มันไม่ได้เริ่มต้นอย่างขี้เกียจและมันก็โอเคที่จะทำซ้ำหลาย ๆ ครั้ง)
  • ถ้านับของรายการที่เป็นที่รู้จักกันก็ไม่ได้ซ่อน knowlegde ที่เหมือนIEnumerable<T>ไม่
  • เนื่องจากลูกค้าไม่ทราบว่ามีการนำไปปฏิบัติอะไรIEnumerableหรือIReadOnlyCollectไม่สามารถสันนิษฐานได้ว่าไม่มีการเปลี่ยนแปลงใด ๆ (กล่าวอีกนัยหนึ่งไม่รับประกันว่ารายการของชุดสะสมจะไม่ถูกเปลี่ยนแปลงในขณะที่การอ้างอิงไปยังชุดสะสมนั้นไม่มีการเปลี่ยนแปลง)

นอกจากนี้หาก APi ต้องการแจ้งให้ลูกค้าทราบเกี่ยวกับการเปลี่ยนแปลงในคอลเลกชันฉันจะเปิดเผยกิจกรรมในฐานะสมาชิกแยกต่างหาก

โปรดทราบว่าฉันไม่ได้บอกว่าIEnumerable<T>ไม่ดีโดยทั่วไป ฉันยังคงใช้มันเมื่อผ่านการรวบรวมเป็นอาร์กิวเมนต์หรือคืนค่าเริ่มต้นการรวบรวมอย่างเกียจคร้าน (ในกรณีนี้มันสมเหตุสมผลที่จะซ่อนจำนวนรายการเพราะยังไม่ทราบ)

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