ชั้นเรียนที่ไม่เปลี่ยนแปลงกลายเป็นภาระในจุดใด?


16

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

ตัวอย่างเช่นนี่คือคลาสที่ไม่เปลี่ยนรูปเพื่อแสดงสิ่งที่มีชื่อ (ฉันใช้ไวยากรณ์ C # แต่หลักการใช้กับภาษา OO ทั้งหมด)

class NamedThing
{
    private string _name;    
    public NamedThing(string name)
    {
        _name = name;
    }    
    public NamedThing(NamedThing other)
    {
         this._name = other._name;
    }
    public string Name
    {
        get { return _name; }
    }
}

สิ่งที่สร้างชื่อสามารถสร้างสอบถามและคัดลอกไปยังสิ่งที่ตั้งชื่อใหม่ แต่ไม่สามารถเปลี่ยนชื่อได้

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

หากคลาสนั้นอาจมีแอ็ตทริบิวต์และคอลเลกชันที่มีคลาสที่ซับซ้อนอื่น ๆ ดูเหมือนว่ารายการพารามิเตอร์ตัวสร้างจะกลายเป็นฝันร้าย

ดังนั้นสิ่งที่ชั้นไม่กลายเป็นซับซ้อนเกินไปที่จะไม่เปลี่ยนรูป?


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

1
คุณสามารถดูรูปแบบการสร้างข้อเสนอแนะในคำถามนี้: stackoverflow.com/questions/1304154/...
มด

คุณดู MemberwiseClone แล้วหรือยัง คุณไม่ต้องอัปเดตตัวสร้างสำเนาสำหรับสมาชิกใหม่แต่ละคน
kevin cline

3
@Tony หากคอลเลกชันและทุกสิ่งที่พวกเขามียังไม่เปลี่ยนรูปคุณไม่จำเป็นต้องคัดลอกลึกสำเนาตื้น ๆ ก็เพียงพอแล้ว
mjcopple

2
นอกเหนือจากนี้ฉันมักจะใช้ฟิลด์ "set-times" ในคลาสที่คลาสต้องมี "ค่อนข้าง" ไม่เปลี่ยนรูป แต่ไม่สมบูรณ์ ฉันพบว่าสิ่งนี้ช่วยแก้ปัญหาของตัวสร้างขนาดใหญ่ แต่ให้ประโยชน์ส่วนใหญ่ของคลาสที่ไม่เปลี่ยนรูป (กล่าวคือรหัสชั้นภายในของคุณไม่ต้องกังวลเกี่ยวกับการเปลี่ยนแปลงค่า)
Earlz

คำตอบ:


23

เมื่อพวกเขากลายเป็นภาระ? เร็วมาก (เป็นพิเศษหากภาษาที่คุณเลือกไม่ได้ให้การสนับสนุนทางวากยสัมพันธ์เพียงพอสำหรับการไม่เปลี่ยนรูป)

ความไม่สามารถเปลี่ยนแปลงได้นั้นถูกขายเป็นกระสุนเงินสำหรับภาวะที่กลืนไม่เข้าคายไม่ออกแบบ multi-core และทั้งหมดนั้น แต่ความไม่สามารถเปลี่ยนแปลงได้ในภาษา OO ส่วนใหญ่บังคับให้คุณเพิ่มสิ่งประดิษฐ์และการปฏิบัติที่ประดิษฐ์ในรูปแบบและกระบวนการของคุณ สำหรับคลาสที่ไม่เปลี่ยนรูปแบบที่ซับซ้อนแต่ละคลาสคุณต้องมีตัวสร้างที่ซับซ้อนเท่ากัน (อย่างน้อยภายใน) ไม่ว่าคุณจะออกแบบมันยังคงแนะนำการมีเพศสัมพันธ์ที่แข็งแกร่ง (ดังนั้นเราจึงมีเหตุผลที่ดีที่จะแนะนำพวกเขา)

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

มันยังแย่กว่าเดิมเมื่อผู้คนคิดว่าการเปลี่ยนแปลงไม่ได้ในภาษาที่ใช้ทั่วไปเช่น Java หรือ C # ทำให้ทุกอย่างไม่เปลี่ยนรูป จากนั้นเป็นผลให้คุณเห็นคนที่บังคับให้สร้างการแสดงออกในภาษาที่ไม่สนับสนุนสิ่งดังกล่าวได้อย่างง่ายดาย

วิศวกรรมเป็นการกระทำของการสร้างแบบจำลองผ่านการประนีประนอมและการแลกเปลี่ยน การทำให้ทุกอย่างไม่เปลี่ยนรูปโดยการประกาศเพราะมีคนอ่านว่าทุกอย่างไม่เปลี่ยนรูปแบบในภาษาเชิงหน้าที่ X หรือ Y นั่นไม่ใช่วิศวกรรมที่ดี

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

การเปลี่ยนไม่ได้จะกลายเป็นภาระที่รวดเร็วจริงๆ (มันเพิ่มความซับซ้อนโดยไม่ตั้งใจ) ถ้าทำได้โดยไม่มีเหตุผลเมื่อทำนอกสิ่งที่สมเหตุสมผลในบริบทของแบบจำลองโดเมน

ฉันพยายามหลีกเลี่ยงมัน (ยกเว้นว่าฉันกำลังทำงานในภาษาโปรแกรมที่มีการสนับสนุนทางไวยากรณ์ที่ดีสำหรับมัน)


6
คุณเคยสังเกตไหมว่าคำตอบที่ถูกต้องเป็นลายลักษณ์อักษรที่ถูกต้องในทางปฏิบัติเขียนด้วยคำอธิบายของหลักการทางวิศวกรรมที่เรียบง่าย แต่มีแนวโน้มที่จะไม่ได้รับคะแนนเสียงมากเท่าที่ใช้การเขียนโค้ดที่ทันสมัย? นี่เป็นคำตอบที่ยอดเยี่ยมและยอดเยี่ยม
Huperniketes

3
ขอบคุณ :) ฉันสังเกตเห็นแนวโน้มของตัวฉันเอง แต่ก็ไม่เป็นไร แฟชั่น fanboys ปั่นรหัสที่เราได้รับการซ่อมแซมในภายหลังในอัตราชั่วโมงที่ดีกว่า hahah :) jk ...
luis.espinal

4
กระสุนเงิน? ไม่ คุ้มค่ากับความอึดอัดเล็กน้อยใน C # / Java (มันไม่ได้เลวร้ายจริงๆ)? อย่างแน่นอน นอกจากนี้บทบาทของมัลติคอร์ในการเปลี่ยนแปลงไม่ได้ค่อนข้างน้อย ... ประโยชน์ที่แท้จริงคือความสะดวกในการให้เหตุผล
Mauricio Scheffer

@Mauricio - ถ้าคุณพูดอย่างนั้น (การเปลี่ยนไม่ได้ใน Java นั้นไม่เลวเลย) ทำงานกับ Java ตั้งแต่ปี 1998 ถึง 2011 ฉันขอแตกต่างกันมันไม่ได้รับการยกเว้นเล็กน้อยในฐานรหัสแบบง่าย อย่างไรก็ตามผู้คนมีประสบการณ์ที่แตกต่างกันและฉันยอมรับว่า POV ของฉันไม่ได้เป็นอิสระจากการกระทำ ขออภัยไม่สามารถตกลงที่นั่น อย่างไรก็ตามฉันเห็นด้วยกับความสะดวกในการให้เหตุผลว่าเป็นสิ่งที่สำคัญที่สุดเมื่อพูดถึงการไม่เปลี่ยนรูป
luis.espinal

13

ฉันผ่านขั้นตอนการยืนยันในชั้นเรียนว่าไม่เปลี่ยนรูปเมื่อเป็นไปได้ มีผู้สร้างสำหรับทุกอย่างสวยมากอาร์เรย์ที่ไม่เปลี่ยนรูป ฯลฯ ฯลฯ ฉันพบคำตอบสำหรับคำถามของคุณนั้นง่าย: ณ จุดไหนคลาสที่ไม่เปลี่ยนรูปจะกลายเป็นภาระ? เร็วมาก ทันทีที่คุณต้องการซีเรียลไลซ์บางสิ่งคุณจะต้องหยุดการซีเรียลไลซ์ซึ่งหมายความว่ามันจะต้องไม่แน่นอน ทันทีที่คุณต้องการใช้ ORM ส่วนใหญ่ยืนยันในคุณสมบัติที่ไม่แน่นอน และอื่น ๆ

ในที่สุดฉันก็เปลี่ยนนโยบายนั้นเป็นอินเทอร์เฟซที่ไม่เปลี่ยนรูปแบบเป็นวัตถุที่ไม่แน่นอน

class NamedThing : INamedThing
{
    private string _name;    
    public NamedThing(string name)
    {
        _name = name;
    }    

    public NamedThing(NamedThing other)
    {
        this._name = other._name;
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

interface INamedThing
{
    string Name { get; }
}

ตอนนี้วัตถุมีความยืดหยุ่น แต่คุณยังสามารถบอกรหัสการโทรได้ว่าไม่ควรแก้ไขคุณสมบัติเหล่านี้


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

1
@Aaraught - จุดที่น่าสนใจ ฉันคิดว่ามันเป็นเรื่องทางจิตวิทยามากกว่าการปกป้องที่แท้จริง อย่างไรก็ตามบรรทัดสุดท้ายของคุณมีหลักฐานปลอม เหตุผลที่ทำให้มันไม่แน่นอนคือสิ่งต่าง ๆ จำเป็นต้องยกตัวอย่างและเติมผ่านการสะท้อนไม่ใช่เพื่อให้กลายพันธุ์ทันที
pdr

1
@Aaronaught: เช่นเดียวกับวิธีIComparable<T>การค้ำประกันว่าถ้าX.CompareTo(Y)>0และแล้วY.CompareTo(Z)>0 อินเตอร์เฟซที่มีสัญญา หากสัญญาสำหรับระบุว่าค่าของรายการและทรัพย์สินทั้งหมดจะต้อง "ตั้งอยู่ในหิน" ก่อนที่อินสแตนซ์ใด ๆ ที่สัมผัสกับโลกภายนอกจากนั้นการใช้งานที่ถูกต้องทั้งหมดจะทำเช่นนั้น ไม่มีสิ่งใดขัดขวางการใช้งานจากการละเมิดความไว แต่การใช้งานที่ผิดกฎหมาย หากความผิดปกติเกิดขึ้นเมื่อได้รับสิ่งผิดกฎหมาย...X.CompareTo(Z)>0IImmutableList<T>IComparableSortedDictionaryIComparable
supercat

1
@Aaronaught: ทำไมฉันจึงควรมีความมั่นใจว่าการใช้งานIReadOnlyList<T>จะไม่เปลี่ยนรูปที่ระบุว่า (1) ไม่มีข้อกำหนดดังกล่าวมีการระบุไว้ในเอกสารอินเตอร์เฟซและ (2) การดำเนินงานที่พบมากที่สุดList<T>, ไม่ได้อ่านอย่างเดียว ? ฉันไม่ชัดเจนว่ามีอะไรคลุมเครือเกี่ยวกับคำของฉัน: คอลเลกชันสามารถอ่านได้หากข้อมูลภายในสามารถอ่านได้ มันเป็นแบบอ่านอย่างเดียวหากสามารถให้สัญญาได้ว่าข้อมูลที่มีอยู่ไม่สามารถเปลี่ยนแปลงได้เว้นแต่ว่าจะมีการอ้างอิงภายนอกบางอย่างจากรหัสซึ่งจะเปลี่ยน มันไม่เปลี่ยนรูปถ้ามันสามารถรับประกันได้ว่ามันไม่สามารถเปลี่ยนแปลงระยะเวลา
supercat

1
@supercat: อนึ่ง Microsoft เห็นด้วยกับฉัน พวกเขาเปิดตัวแพคเกจคอลเลกชันที่ไม่เปลี่ยนรูปแบบและสังเกตว่ามันเป็นรูปแบบที่เป็นรูปธรรมเพราะคุณไม่สามารถรับประกันได้ว่าคลาสนามธรรมหรืออินเทอร์เฟซจะไม่เปลี่ยนรูปแบบอย่างแท้จริง
Aaronaught

8

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

โปรดสังเกตว่าคลาสที่ซับซ้อนเกินไปหรือรายการพารามิเตอร์เมธอดแบบยาวคือการออกแบบกลิ่นต่อ se โดยไม่คำนึงถึงความไม่สามารถเปลี่ยนแปลงได้

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


5

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

class MyClass
{
    struct Memento
    {
        public int field1;
        public string field2;
    }

    private readonly Memento memento;

    public MyClass(int field1, string field2)
    {
        this.memento = new Memento()
            {
                field1 = field1,
                field2 = field2
            };
    }

    private MyClass(Memento memento) // for copying
    {
        this.memento = memento;
    }

    public int Field1 { get { return this.memento.field1; } }
    public string Field2 { get { return this.memento.field2; } }

    public MyClass WithNewField1(int newField1)
    {
        Memento newMemento = this.memento;
        newMemento.field1 = newField1;
        return new MyClass(newMemento);
    }
}

ฉันคิดว่าโครงสร้างภายในไม่จำเป็น เป็นอีกวิธีในการทำ MemberwiseClone
Codism

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

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

... มันมีประโยชน์มากกับผู้ถือข้อมูลที่ไม่แน่นอน วิธีการนี้ยังทำให้มันง่ายมากที่จะมีคลาสที่ "ไม่แน่นอน" ที่ไม่แน่นอนและไม่เปลี่ยนรูปแบบ (มาจากฐาน "ที่อ่านได้" ที่เป็นนามธรรม) และให้คอนสตรัคเตอร์สามารถคัดลอกข้อมูลจากกันและกันได้
supercat

3

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

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

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

myList = lists:append([[1,2,3], [4,5,6]])
% myList is now [1,2,3,4,5,6]

นั่นอาจเป็นวิธีที่มีสติในการทำงานกับวัตถุที่ซับซ้อนมากขึ้น เมื่อคุณเพิ่มโหนดของต้นไม้ผลลัพธ์จะเป็นแผนผังใหม่ที่มีโหนดเพิ่ม วิธีการในตัวอย่างข้างต้นส่งคืนรายการใหม่ ในตัวอย่างในวรรคนี้tree.add(newNode)จะส่งคืนต้นไม้ใหม่ที่มีโหนดเพิ่ม สำหรับผู้ใช้มันเป็นเรื่องง่ายที่จะทำงานกับ สำหรับนักเขียนห้องสมุดมันน่าเบื่อเมื่อภาษาไม่สนับสนุนการคัดลอกโดยนัย เกณฑ์นั้นขึ้นอยู่กับความอดทนของคุณเอง สำหรับผู้ใช้ห้องสมุดของคุณขีด จำกัด ที่มีสติมากที่สุดที่ฉันพบคือค่าพารามิเตอร์ประมาณสามถึงสี่ตัว


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

0

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

class NamedThing
{
    private string _name;    
    private string _value;
    private NamedThing(string name, string value)
    {
        _name = name;
        _value = value;
    }    
    public NamedThing(NamedThing other)
    {
        this._name = other._name;
        this._value = other._value;
    }
    public string Name
    {
        get { return _name; }
    }

    public static class Builder {
        string _name;
        string _value;

        public void setValue(string value) {
            _value = value;
        }
        public void setName(string name) {
            _name = name;
        }
        public NamedThing newObject() {
            return new NamedThing(_name, _value);
        }
    }
}

ข้อดีคือคุณสามารถสร้างวัตถุใหม่ที่มีค่าแตกต่างกันของชื่อ differtent ได้อย่างง่ายดาย


ฉันคิดว่าผู้สร้างของคุณคงที่ไม่ถูกต้อง หัวข้ออื่นสามารถเปลี่ยนชื่อแบบคงที่หรือค่าคงที่หลังจากที่คุณตั้งเหล่านั้น newObjectแต่ก่อนที่คุณเรียก
ErikE

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

ฉันเห็นสิ่งที่คุณพูดฉันแค่นึกภาพปัญหานี้เพราะมันไม่ได้นำนักพัฒนาไป "ตกอยู่ในหลุมแห่งความสำเร็จ" ความจริงที่ว่ามันใช้ตัวแปรแบบสแตติกหมายความว่าหาก a Builderถูกใช้ซ้ำมีความเสี่ยงที่แท้จริงของสิ่งที่เกิดขึ้นที่ฉันพูดถึง บางคนอาจกำลังสร้างวัตถุจำนวนมากและตัดสินใจว่าเนื่องจากคุณสมบัติส่วนใหญ่เหมือนกันเพียงแค่นำมาใช้ซ้ำBuilderและในความเป็นจริงเรามาทำให้มันกลายเป็นซิงเกิลระดับโลกที่ถูกฉีดขึ้นอยู่กับการพึ่งพา! อ๊ะ ข้อบกพร่องที่สำคัญแนะนำ ดังนั้นฉันคิดว่ารูปแบบของการผสมอินสแตนซ์กับสแตติกนี้ไม่ดี
ErikE

1
@Salandur Inner classes ใน C # จะเป็น "static" เสมอในความรู้สึกภายในของ Java
Sebastian Redl

0

ดังนั้นสิ่งที่ชั้นไม่กลายเป็นซับซ้อนเกินไปที่จะไม่เปลี่ยนรูป?

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

โครงสร้างข้อมูลถาวร

NamedThingsฉันพบการใช้งานส่วนบุคคลสำหรับการเปลี่ยนไม่ได้เป็นใหญ่โครงสร้างข้อมูลกลางที่รวบรวมพวงของข้อมูลที่เล็กเช่นกรณีของชั้นเรียนที่คุณกำลังแสดงเช่นหนึ่งที่ร้านล้าน โดยเป็นของโครงสร้างข้อมูลถาวรซึ่งไม่เปลี่ยนรูปและอยู่ด้านหลังส่วนต่อประสานที่อนุญาตให้เข้าถึงแบบอ่านอย่างเดียวเท่านั้นองค์ประกอบที่เป็นของคอนเทนเนอร์จะไม่เปลี่ยนรูปโดยไม่ต้องมีคลาสองค์ประกอบ ( NamedThing) ที่ต้องจัดการกับมัน

สำเนาถูก

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

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

ภาระ

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

void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
     // Transform stuff in the range, [first, last).
     for (; first != last; ++first)
          transform(stuff[first]);
}

... จากนั้นจะต้องมีการเขียนเช่นนี้:

ImmList<Stuff> transform_stuff(ImmList<Stuff> stuff, int first, int last)
{
     // Grab a "transient" (builder) list we can modify:
     TransientList<Stuff> transient(stuff);

     // Transform stuff in the range, [first, last)
     // for the transient list.
     for (; first != last; ++first)
          transform(transient[first]);

     // Commit the modifications to get and return a new
     // immutable list.
     return stuff.commit(transient);
}

แต่เพื่อแลกกับโค้ดพิเศษสองบรรทัดตอนนี้ฟังก์ชั่นนี้ปลอดภัยในการโทรข้ามเธรดที่มีรายการดั้งเดิมเหมือนกันมันไม่ทำให้เกิดผลข้างเคียง ฯลฯ นอกจากนี้ยังทำให้การดำเนินการนี้เป็นเรื่องง่ายสำหรับผู้ใช้ตั้งแต่ การเลิกทำสามารถเก็บสำเนารายการเก่าที่ราคาไม่แพงได้

ข้อยกเว้นความปลอดภัยหรือการกู้คืนข้อผิดพลาด

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

หากคุณต้องการทำให้ฟังก์ชั่นการกลายพันธุ์เป็นข้อยกเว้นที่ปลอดภัยแล้วมันต้องใช้ตรรกะการย้อนกลับซึ่งการดำเนินการที่ง่ายที่สุดนั้นต้องมีการคัดลอกรายการทั้งหมด :

void transform_stuff(MutList<Stuff>& stuff, int first, int last)
{
    // Make a copy of the whole massive gigabyte-sized list 
    // in case we encounter an exception and need to rollback
    // changes.
    MutList<Stuff> old_stuff = stuff;

    try
    {
         // Transform stuff in the range, [first, last).
         for (; first != last; ++first)
             transform(stuff[first]);
    }
    catch (...)
    {
         // If the operation failed and ran into an exception,
         // swap the original list with the one we modified
         // to "undo" our changes.
         stuff.swap(old_stuff);
         throw;
    }
}

ณ จุดนี้เวอร์ชันที่ยกเว้นความปลอดภัยจะมีราคาสูงกว่าการคำนวณและเนื้อหาที่ยากต่อการเขียนอย่างถูกต้องกว่าเวอร์ชั่นที่ไม่เปลี่ยนรูปโดยใช้ "ผู้สร้าง" และนักพัฒนา C ++ หลายคนละเลยข้อยกเว้นด้านความปลอดภัยและอาจเป็นเรื่องดีสำหรับโดเมนของพวกเขา แต่ในกรณีของฉันฉันต้องการให้แน่ใจว่าโค้ดของฉันทำงานอย่างถูกต้องแม้ในกรณีที่มีข้อยกเว้น (แม้แต่เขียนการทดสอบที่จงใจโยนข้อยกเว้นเพื่อทดสอบข้อยกเว้น ความปลอดภัย) และนั่นทำให้ฉันต้องสามารถย้อนกลับผลข้างเคียงใด ๆ ฟังก์ชั่นทำให้เกิดครึ่งทางในการทำงานถ้ามีอะไรเกิดขึ้น

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

คุณไม่ต้องกังวลกับการย้อนกลับของผลข้างเคียงในฟังก์ชั่นที่ไม่ทำให้เกิดปัญหาใด ๆ !

ดังนั้นกลับไปที่คำถามพื้นฐาน:

ชั้นเรียนที่ไม่เปลี่ยนแปลงกลายเป็นภาระในจุดใด?

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

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

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