เหตุใดคุณจึงควรใช้คลาสที่ซ้อนกันใน. net หรือไม่ควร?


95

ในบล็อกโพสต์ของ Kathleen Dollard ในปี 2008เธอนำเสนอเหตุผลที่น่าสนใจในการใช้ชั้นเรียนที่ซ้อนกันใน. net อย่างไรก็ตามเธอยังกล่าวด้วยว่า FxCop ไม่ชอบชั้นเรียนที่ซ้อนกัน ฉันคิดว่าคนที่เขียนกฎ FxCop ไม่ได้โง่ดังนั้นต้องมีเหตุผลที่อยู่เบื้องหลังตำแหน่งนั้น แต่ฉันไม่สามารถหามันได้


ลิงก์ Wayback Archive ไปยังบล็อกโพสต์: web.archive.org/web/20141127115939/https://blogs.msmvps.com/…
iokevins

ดังที่nawfal ชี้ให้เห็นเพื่อนของเราEric Lippert ได้ตอบคำถามนี้ซ้ำกันที่นี่คำตอบที่เป็นปัญหาเริ่มต้นด้วย " ใช้ชั้นเรียนที่ซ้อนกันเมื่อคุณต้องการคลาสตัวช่วยที่ไม่มีความหมายนอกชั้นเรียนโดยเฉพาะอย่างยิ่งเมื่อชั้นเรียนที่ซ้อนกันสามารถใช้ประโยชน์จาก รายละเอียดการใช้งานส่วนตัวของคลาสนอกข้อโต้แย้งของคุณที่ว่าคลาสที่ซ้อนกันนั้นไร้ประโยชน์ยังเป็นข้อโต้แย้งที่ว่าวิธีการส่วนตัวนั้นไร้ประโยชน์ ... "
ruffin

คำตอบ:


97

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

public class SortedMap {
    private class TreeNode {
        TreeNode left;
        TreeNode right;
    }
}

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

ถ้าคลาส TreeNode อยู่ภายนอกคุณอาจต้องสร้างฟิลด์ทั้งหมดpublicหรือสร้างget/setวิธีการมากมายเพื่อใช้ โลกภายนอกจะมีอีกชั้นหนึ่งที่ก่อให้เกิดมลพิษทางเพศของพวกเขา


44
ในการเพิ่มสิ่งนี้: คุณยังสามารถใช้คลาสบางส่วนในไฟล์แยกต่างหากเพื่อจัดการโค้ดของคุณให้ดีขึ้น ใส่คลาสภายในในไฟล์แยกต่างหาก (ในกรณีนี้คือ SortedMap.TreeNode.cs) สิ่งนี้ควรรักษารหัสของคุณให้สะอาดในขณะเดียวกันก็แยกรหัสของคุณด้วย :)
Erik van Brakel

1
จะมีบางกรณีที่คุณจะต้องทำให้คลาสที่ซ้อนกันเป็นแบบสาธารณะหรือภายในหากมีการใช้ในประเภทการส่งคืน API สาธารณะหรือคุณสมบัติสาธารณะของคลาสคอนเทนเนอร์ ฉันไม่แน่ใจว่ามันเป็นแนวทางปฏิบัติที่ดีหรือเปล่า กรณีดังกล่าวอาจเหมาะสมกว่าที่จะดึงคลาสที่ซ้อนกันออกนอกคลาสคอนเทนเนอร์ System.Windows.Forms.ListViewItem.ListViewSubItem คลาสใน. Net framework เป็นตัวอย่างหนึ่ง
RBT

16

จากบทช่วยสอน Java ของ Sun:

เหตุใดจึงต้องใช้คลาสที่ซ้อนกัน มีเหตุผลที่น่าสนใจหลายประการในการใช้คลาสที่ซ้อนกัน ได้แก่ :

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

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

การห่อหุ้มที่เพิ่มขึ้น - พิจารณาคลาสระดับบนสุด 2 คลาสคือ A และ B โดยที่ B ต้องการเข้าถึงสมาชิกของ A ที่จะประกาศให้เป็นส่วนตัว โดยการซ่อนคลาส B ไว้ในคลาส A สมาชิกของ A สามารถประกาศเป็นส่วนตัวและ B สามารถเข้าถึงได้ นอกจากนี้ B เองยังสามารถซ่อนตัวจากโลกภายนอกได้ <- สิ่งนี้ใช้ไม่ได้กับการใช้งานคลาสที่ซ้อนกันของ C # ซึ่งใช้ได้กับ Java เท่านั้น

โค้ดที่อ่านง่ายและบำรุงรักษาได้มากขึ้นการซ้อนชั้นเรียนขนาดเล็กภายในคลาสระดับบนสุดจะทำให้โค้ดอยู่ใกล้กับที่ที่ใช้มากขึ้น


1
สิ่งนี้ใช้ไม่ได้จริง ๆ เนื่องจากคุณไม่สามารถเข้าถึงตัวแปรอินสแตนซ์จากคลาสที่ปิดล้อมใน C # เหมือนที่คุณทำได้ใน Java สามารถเข้าถึงได้เฉพาะสมาชิกแบบคงที่เท่านั้น
Ben Baron

5
อย่างไรก็ตามหากคุณส่งอินสแตนซ์ของคลาสที่ปิดล้อมไปยังคลาสที่ซ้อนกันคลาสที่ซ้อนกันจะสามารถเข้าถึงสมาชิกทั้งหมดผ่านตัวแปรอินสแตนซ์นั้นได้อย่างเต็มที่ ... ดังนั้นมันเหมือนกับว่า Java ทำให้ตัวแปรอินสแตนซ์เป็นนัยในขณะที่ใน C # คุณต้อง ทำให้ชัดเจน
Alex

@Alex ไม่มันไม่ใช่ใน Java คลาสที่ซ้อนกันจะจับอินสแตนซ์คลาสพาเรนต์เมื่อสร้างอินสแตนซ์ซึ่งหมายความว่ามันป้องกันไม่ให้พาเรนต์ถูกรวบรวมขยะ นอกจากนี้ยังหมายความว่าคลาสที่ซ้อนกันไม่สามารถสร้างอินสแตนซ์ได้หากไม่มีคลาสหลัก ดังนั้นไม่สิ่งเหล่านี้ไม่เหมือนกันเลย
Tomáš Zato - คืนสถานะ Monica

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

9

รูปแบบซิงเกิลตันที่ขี้เกียจและปลอดภัย

public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }
    
    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

แหล่งที่มา: https://csharpindepth.com/Articles/Singleton


5

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

ถ้าผู้เรียกใช้ชั้นเรียน (ภายนอก) ฉันมักจะชอบทำให้เป็นชั้นเรียนแบบสแตนด์อโลนแยกต่างหาก


5

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

public  class       BaseDataObject
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  class       BaseDataObjectList
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
:   
                    CollectionBase<tDataObject>
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  interface   IBaseBusiness
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  interface   IBaseDataAccess
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

เราสามารถลดความซับซ้อนของลายเซ็นของคลาสเหล่านี้โดยใช้เนมสเปซทั่วไป (ดำเนินการผ่านคลาสที่ซ้อนกัน):

public
partial class   Entity
                <
                    tDataObject, 
                    tDataObjectList, 
                    tBusiness, 
                    tDataAccess
                >
        where   tDataObject     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
        where   tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
        where   tBusiness       : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
        where   tDataAccess     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{

    public  class       BaseDataObject {}

    public  class       BaseDataObjectList : CollectionBase<tDataObject> {}

    public  interface   IBaseBusiness {}

    public  interface   IBaseDataAccess {}

}

จากนั้นด้วยการใช้คลาสบางส่วนตามที่ Erik van Brakel แนะนำในความคิดเห็นก่อนหน้านี้คุณสามารถแยกคลาสออกเป็นไฟล์ที่ซ้อนกันแยกกันได้ ฉันขอแนะนำให้ใช้ส่วนขยาย Visual Studio เช่น NestIn เพื่อรองรับการซ้อนไฟล์คลาสบางส่วน ซึ่งทำให้สามารถใช้ไฟล์คลาส "เนมสเปซ" เพื่อจัดระเบียบไฟล์คลาสที่ซ้อนกันในโฟลเดอร์ได้เช่นกัน

ตัวอย่างเช่น:

Entity.cs

public
partial class   Entity
                <
                    tDataObject, 
                    tDataObjectList, 
                    tBusiness, 
                    tDataAccess
                >
        where   tDataObject     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
        where   tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
        where   tBusiness       : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
        where   tDataAccess     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
}

Entity.BaseDataObject.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  class   BaseDataObject
    {

        public  DataTimeOffset  CreatedDateTime     { get; set; }
        public  Guid            CreatedById         { get; set; }
        public  Guid            Id                  { get; set; }
        public  DataTimeOffset  LastUpdateDateTime  { get; set; }
        public  Guid            LastUpdatedById     { get; set; }

        public
        static
        implicit    operator    tDataObjectList(DataObject dataObject)
        {
            var returnList  = new tDataObjectList();
            returnList.Add((tDataObject) this);
            return returnList;
        }

    }

}

Entity.BaseDataObjectList.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  class   BaseDataObjectList : CollectionBase<tDataObject>
    {

        public  tDataObjectList ShallowClone() 
        {
            var returnList  = new tDataObjectList();
            returnList.AddRange(this);
            return returnList;
        }

    }

}

Entity.IBaseBusiness.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseBusiness
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}

Entity.IBaseDataAccess.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseDataAccess
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}

ไฟล์ใน Visual Studio Solution Explorer จะถูกจัดระเบียบดังนี้:

Entity.cs
+   Entity.BaseDataObject.cs
+   Entity.BaseDataObjectList.cs
+   Entity.IBaseBusiness.cs
+   Entity.IBaseDataAccess.cs

และคุณจะใช้เนมสเปซทั่วไปดังต่อไปนี้:

User.cs

public
partial class   User
:
                Entity
                <
                    User.DataObject, 
                    User.DataObjectList, 
                    User.IBusiness, 
                    User.IDataAccess
                >
{
}

User.DataObject.cs

partial class   User
{

    public  class   DataObject : BaseDataObject 
    {
        public  string  UserName            { get; set; }
        public  byte[]  PasswordHash        { get; set; }
        public  bool    AccountIsEnabled    { get; set; }
    }

}

User.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

User.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

User.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

และไฟล์จะถูกจัดระเบียบในตัวสำรวจโซลูชันดังนี้:

User.cs
+   User.DataObject.cs
+   User.DataObjectList.cs
+   User.IBusiness.cs
+   User.IDataAccess.cs

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


3

ถ้าฉันเข้าใจบทความของ Katheleen ถูกต้องเธอเสนอให้ใช้คลาสซ้อนกันเพื่อให้สามารถเขียน SomeEntity.Collection แทน EntityCollection <SomeEntity> ในความคิดของฉันมันเป็นวิธีที่ขัดแย้งในการช่วยคุณประหยัดเวลาในการพิมพ์ ฉันค่อนข้างมั่นใจว่าในคอลเล็กชันแอปพลิเคชันในโลกแห่งความเป็นจริงจะมีความแตกต่างในการใช้งานดังนั้นคุณจะต้องสร้างคลาสแยก ฉันคิดว่าการใช้ชื่อคลาสเพื่อ จำกัด ขอบเขตคลาสอื่นไม่ใช่ความคิดที่ดี ก่อให้เกิดมลภาวะและเสริมสร้างการพึ่งพาระหว่างชั้นเรียน การใช้เนมสเปซเป็นวิธีมาตรฐานในการควบคุมขอบเขตคลาส อย่างไรก็ตามฉันพบว่าการใช้คลาสที่ซ้อนกันเช่นในความคิดเห็น @hazzen นั้นยอมรับได้เว้นแต่คุณจะมีคลาสที่ซ้อนกันมากมายซึ่งเป็นสัญญาณของการออกแบบที่ไม่ดี


1

ฉันมักใช้คลาสที่ซ้อนกันเพื่อซ่อนรายละเอียดการใช้งาน ตัวอย่างจากคำตอบของ Eric Lippert ที่นี่:

abstract public class BankAccount
{
    private BankAccount() { }
    // Now no one else can extend BankAccount because a derived class
    // must be able to call a constructor, but all the constructors are
    // private!
    private sealed class ChequingAccount : BankAccount { ... }
    public static BankAccount MakeChequingAccount() { return new ChequingAccount(); }
    private sealed class SavingsAccount : BankAccount { ... }
}

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

Equality<Person>.CreateComparer(p => p.Id);

แทน

new EqualityComparer<Person, int>(p => p.Id);

นอกจากนี้ฉันสามารถมีรายการทั่วไปEquality<Person>แต่ไม่มีEqualityComparer<Person, int>

var l = new List<Equality<Person>> 
        { 
         Equality<Person>.CreateComparer(p => p.Id),
         Equality<Person>.CreateComparer(p => p.Name) 
        }

โดยที่

var l = new List<EqualityComparer<Person, ??>>> 
        { 
         new EqualityComparer<Person, int>>(p => p.Id),
         new EqualityComparer<Person, string>>(p => p.Name) 
        }

เป็นไปไม่ได้ นั่นคือประโยชน์ของคลาสซ้อนที่สืบทอดมาจากคลาสแม่

อีกกรณีหนึ่ง (ในลักษณะเดียวกัน - การซ่อนการใช้งาน) คือเมื่อคุณต้องการทำให้สมาชิกของคลาส (ฟิลด์คุณสมบัติ ฯลฯ ) สามารถเข้าถึงได้สำหรับคลาสเดียวเท่านั้น:

public class Outer 
{
   class Inner //private class
   {
       public int Field; //public field
   }

   static inner = new Inner { Field = -1 }; // Field is accessible here, but in no other class
}

1

การใช้งานอื่นที่ยังไม่ได้กล่าวถึงสำหรับคลาสที่ซ้อนกันคือการแยกประเภททั่วไป ตัวอย่างเช่นสมมติว่าคน ๆ หนึ่งต้องการมีตระกูลทั่วไปของคลาสคงที่ซึ่งสามารถใช้เมธอดที่มีพารามิเตอร์จำนวนต่างๆพร้อมกับค่าสำหรับพารามิเตอร์บางตัวและสร้างผู้รับมอบสิทธิ์ที่มีพารามิเตอร์น้อยลง ตัวอย่างเช่นคนหนึ่งต้องการที่จะมีวิธีการคงที่ซึ่งสามารถรับAction<string, int, double>และให้ผลString<string, int>ซึ่งจะเรียกการดำเนินการที่ให้มาผ่าน 3.5 เป็นdouble; หนึ่งอาจต้องการที่จะมีวิธีการแบบคงที่สามารถใช้เป็นนักการAction<string, int, double>และผลผลิตAction<string>ผ่าน7เป็นintและเป็น5.3 doubleการใช้คลาสที่ซ้อนกันทั่วไปเราสามารถจัดให้มีการเรียกใช้เมธอดเป็นดังนี้:

MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);

หรือเนื่องจากสามารถอนุมานประเภทหลังในแต่ละนิพจน์ได้แม้ว่าแบบเดิมจะไม่สามารถ:

MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);

การใช้ประเภททั่วไปที่ซ้อนกันทำให้สามารถบอกได้ว่าผู้รับมอบสิทธิ์ใดสามารถใช้ได้กับส่วนใดของคำอธิบายประเภทโดยรวม


1

คลาสที่ซ้อนกันสามารถใช้สำหรับความต้องการต่อไปนี้:

  1. การจำแนกประเภทของข้อมูล
  2. เมื่อตรรกะของคลาสหลักซับซ้อนและคุณรู้สึกว่าคุณต้องการอ็อบเจ็กต์รองในการจัดการคลาส
  3. เมื่อคุณเห็นว่าสถานะและการดำรงอยู่ของคลาสนั้นขึ้นอยู่กับคลาสปิดล้อม

0

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


0

ฉันชอบที่จะซ้อนข้อยกเว้นที่ไม่ซ้ำกับคลาสเดียวเช่น สิ่งที่ไม่เคยถูกโยนทิ้งจากที่อื่น

ตัวอย่างเช่น:

public class MyClass
{
    void DoStuff()
    {
        if (!someArbitraryCondition)
        {
            // This is the only class from which OhNoException is thrown
            throw new OhNoException(
                "Oh no! Some arbitrary condition was not satisfied!");
        }
        // Do other stuff
    }

    public class OhNoException : Exception
    {
        // Constructors calling base()
    }
}

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


0

โปรดทราบว่าคุณจะต้องทดสอบคลาสที่ซ้อนกัน หากเป็นแบบส่วนตัวคุณจะไม่สามารถทดสอบแยกต่างหากได้

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

ดังนั้นคุณอาจต้องการใช้เฉพาะคลาสซ้อนส่วนตัวที่มีความซับซ้อนต่ำ


0

ใช่สำหรับกรณีนี้:

class Join_Operator
{

    class Departamento
    {
        public int idDepto { get; set; }
        public string nombreDepto { get; set; }
    }

    class Empleado
    {
        public int idDepto { get; set; }
        public string nombreEmpleado { get; set; }
    }

    public void JoinTables()
    {
        List<Departamento> departamentos = new List<Departamento>();
        departamentos.Add(new Departamento { idDepto = 1, nombreDepto = "Arquitectura" });
        departamentos.Add(new Departamento { idDepto = 2, nombreDepto = "Programación" });

        List<Empleado> empleados = new List<Empleado>();
        empleados.Add(new Empleado { idDepto = 1, nombreEmpleado = "John Doe." });
        empleados.Add(new Empleado { idDepto = 2, nombreEmpleado = "Jim Bell" });

        var joinList = (from e in empleados
                        join d in departamentos on
                        e.idDepto equals d.idDepto
                        select new
                        {
                            nombreEmpleado = e.nombreEmpleado,
                            nombreDepto = d.nombreDepto
                        });
        foreach (var dato in joinList)
        {
            Console.WriteLine("{0} es empleado del departamento de {1}", dato.nombreEmpleado, dato.nombreDepto);
        }
    }
}

ทำไม? เพิ่มบริบทให้กับโค้ดในโซลูชันของคุณเพื่อช่วยให้ผู้อ่านในอนาคตเข้าใจเหตุผลเบื้องหลังคำตอบของคุณ
Grant Miller

0

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

เพื่อชี้แจงฉันจะแสดงสิ่งนี้ผ่านตัวอย่าง:

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

 class Order
    {
        private List<OrderItem> _orderItems = new List<OrderItem>();

        public void AddOrderItem(OrderItem line)
        {
            _orderItems.Add(line);
        }

        public double OrderTotal()
        {
            double total = 0;
            foreach (OrderItem item in _orderItems)
            {
                total += item.TotalPrice();
            }

            return total;
        }

        // Nested class
        public class OrderItem
        {
            public int ProductId { get; set; }
            public int Quantity { get; set; }
            public double Price { get; set; }
            public double TotalPrice() => Price * Quantity;
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            Order order = new Order();

            Order.OrderItem orderItem1 = new Order.OrderItem();
            orderItem1.ProductId = 1;
            orderItem1.Quantity = 5;
            orderItem1.Price = 1.99;
            order.AddOrderItem(orderItem1);

            Order.OrderItem orderItem2 = new Order.OrderItem();
            orderItem2.ProductId = 2;
            orderItem2.Quantity = 12;
            orderItem2.Price = 0.35;
            order.AddOrderItem(orderItem2);

            Console.WriteLine(order.OrderTotal());
            ReadLine();
        }


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