เมื่อใดและทำไมต้องใช้คลาสที่ซ้อนกัน?


30

การใช้การเขียนโปรแกรมเชิงวัตถุเรามีพลังในการสร้างคลาสภายในคลาส (คลาสที่ซ้อนกัน) แต่ฉันไม่เคยสร้างคลาสที่ซ้อนกันในประสบการณ์การเข้ารหัส 4 ปีของฉัน
คลาสที่ซ้อนกันมีประโยชน์อย่างไร?

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

ในสถานการณ์ใดควรใช้คลาสที่ซ้อนกันหรือมีประสิทธิภาพมากกว่าในแง่ของการใช้งานมากกว่าเทคนิคอื่น ๆ ?


1
คุณมีคำตอบที่ดีและบางครั้งฉันก็แค่มีกรรมกรหรือสตรัทที่ฉันต้องการเพียงแค่ในห้องเรียน
paparazzo

คำตอบ:


19

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

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

public interface IFoo 
{
    int Foo{get;}      
}
public class Factory
{
    private class MyFoo : IFoo
    {
        public int Foo{get;set;}
    }
    public IFoo CreateFoo(int value) => new MyFoo{Foo = value};
}

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

ถ้าเราให้อินสแตนซ์ของอินเทอร์เฟซบางอย่างกับวัตถุอื่น แต่เราไม่ต้องการให้คลาสหลักของเราใช้มันเราสามารถปล่อยให้คลาสภายในใช้มันได้

public class Outer
{
    private int _example;
    private class Inner : ISomeInterface
    {
        Outer _outer;
        public Inner(Outer outer){_outer = outer;}
        public int DoStuff() => _outer._example;
    }
    public void DoStuff(){_someDependency.DoBar(new Inner(this)); }
}

ในกรณีส่วนใหญ่ฉันคาดหวังว่าผู้ได้รับมอบหมายจะเป็นวิธีที่สะอาดกว่าในการทำสิ่งที่คุณแสดงในตัวอย่างที่สอง
Ben Aaronson

@BenAaronson คุณจะใช้อินเทอร์เฟซแบบสุ่มโดยใช้ตัวแทนได้อย่างไร
Esben Skov Pedersen

@EsbenSkovPedersen ดีสำหรับตัวอย่างของคุณแทนที่จะผ่านตัวอย่างของOuterคุณจะต้องผ่านFunc<int>ซึ่งจะเป็น() => _example
Ben Aaronson

@BenAaronson ในกรณีที่ง่ายที่สุดนี้คุณพูดถูก แต่สำหรับตัวอย่างที่ซับซ้อนมากขึ้น
Esben Skov Pedersen

@EsbenSkovPedersen: ตัวอย่างของคุณมีข้อดีบางประการ แต่ควรใช้ IMO ในกรณีที่Innerไม่มีการซ้อนกันและinternalไม่ทำงาน (เช่นเมื่อคุณไม่ได้จัดการกับแอสเซมบลีที่แตกต่างกัน) ความสามารถในการอ่านที่อ่านได้จากคลาสการซ้อนจะทำให้การใช้งานน้อยลงinternal(ถ้าเป็นไปได้)
Flater

23

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

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

การใช้ IEnumerable เป็นตัวอย่างที่ดีของสิ่งนี้:

class BlobOfBusinessData: IEnumerable<BusinessDatum>
{
    public IEnumerator<BusinessDatum> GetEnumerator()
    {
         return new BusinessDatumEnumerator(...);
    }

    class BusinessDatumEnumerator: IEnumerator<BusinessDatum>
    {
        ...
    }
}

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

นั่นไม่ได้หมายความว่าเป็นตัวอย่างของ "วิธีปฏิบัติที่ดีที่สุด" ของวิธีการนำไปใช้IEnumerableอย่างถูกต้องเพียงขั้นต่ำ ๆ เพื่อให้ได้แนวคิดดังนั้นฉันจึงทิ้งสิ่งต่าง ๆ เช่นIEnumerable.GetEnumerator()วิธีการที่ชัดเจน


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

3

เหตุใดจึงสร้างคลาสที่ซ้อนกัน

ฉันนึกถึงเหตุผลสำคัญสองสามข้อ:

1. เปิดใช้งานการห่อหุ้ม

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

2. หลีกเลี่ยงมลพิษชื่อ

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

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

publid class LinkedList
{
   class Node { ... }
   // Use Node to implement the LinkedList class.
}

หากคุณตัดสินใจที่จะเลื่อนNodeขึ้นไปอยู่ในขอบเขตเดียวกับที่LinkedListคุณจะมี

public class LinkedListNode
{
}

public class LinkedList
{
  // Use LinkedListNode to implement the class
}

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


0
  1. ฉันใช้คลาสที่ซ้อนกันสาธารณะสำหรับคลาสตัวช่วยที่เกี่ยวข้อง

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. ใช้พวกเขาสำหรับรูปแบบที่เกี่ยวข้อง

    // Class that may or may not be mutable.
    public class MyRecord {
        protected string name;
        public virtual String Name { get => name; set => throw new InvalidOperation(); }
    
        public Mutable {
            public override String { get => name; set => name = value; }
        }
    }
    
    MyRecord mutableRecord = new MyRecord.Mutable();

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

  1. ฉันใช้มันเพื่อบันทึกภายใน

    public class MyClass {
        List<Line> lines = new List<Line>();
    
        public void AddUser( string name, string address ) => lines.Add(new Line { Name = name, Address = address });
    
        class Line { string Name; string Address; }
    }

0

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

Nested Classเพิ่มการห่อหุ้มรวมถึงจะทำให้โค้ดอ่านได้และบำรุงรักษาได้ดีขึ้น

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