Khi nào và tại sao nên sử dụng Lớp học lồng nhau?


30

Sử dụng lập trình hướng đối tượng, chúng ta có khả năng tạo một lớp bên trong một lớp (một lớp lồng nhau), nhưng tôi chưa bao giờ tạo một lớp lồng trong kinh nghiệm mã hóa 4 năm của mình.
Các lớp lồng nhau tốt cho cái gì?

Tôi biết rằng một lớp có thể được đánh dấu là riêng tư nếu nó được lồng và chúng ta có thể truy cập tất cả các thành viên riêng của lớp đó từ lớp chứa. Chúng ta chỉ có thể đặt các biến là riêng tư trong lớp chứa chính nó.
Vậy tại sao tạo một lớp lồng nhau?

Trong các kịch bản nào các lớp lồng nhau nên được sử dụng hoặc chúng mạnh hơn về mặt sử dụng so với các kỹ thuật khác?


1
Bạn có một số câu trả lời tốt và đôi khi tôi sẽ chỉ có một số lớp làm việc hoặc thanh chống mà tôi chỉ cần trong lớp.
paparazzo

Câu trả lời:


19

Tính năng chính của các lớp lồng nhau là chúng có thể truy cập các thành viên riêng của lớp bên ngoài trong khi có toàn bộ sức mạnh của chính lớp đó. Ngoài ra chúng có thể là riêng tư cho phép đóng gói khá mạnh mẽ trong một số trường hợp:

Ở đây chúng tôi khóa setter hoàn toàn xuống nhà máy vì lớp này là riêng tư, không người tiêu dùng nào có thể hạ thấp nó và truy cập vào setter, và chúng tôi có thể kiểm soát hoàn toàn những gì được phép.

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};
}

Ngoài ra, nó rất hữu ích để thực hiện các giao diện của bên thứ ba trong một môi trường được kiểm soát, nơi chúng ta vẫn có thể truy cập các thành viên tư nhân.

Ví dụ, nếu chúng ta cung cấp một thể hiện của một số giao diện cho một số đối tượng khác nhưng chúng ta không muốn lớp chính của chúng ta thực hiện nó, chúng ta có thể để một lớp bên trong thực hiện nó.

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)); }
}

Trong hầu hết các trường hợp, tôi hy vọng các đại biểu sẽ là một cách sạch hơn để thực hiện những gì bạn đang thể hiện trong ví dụ thứ hai
Ben Aaronson

@BenAaronson bạn sẽ triển khai giao diện ngẫu nhiên bằng cách sử dụng các đại biểu như thế nào?
Esben Skov Pedersen

@EsbenSkovPedersen Vâng, ví dụ của bạn, thay vì vượt qua một ví dụ Outer, bạn sẽ vượt qua một Func<int>, điều sẽ xảy ra() => _example
Ben Aaronson

@BenAaronson trong trường hợp cực kỳ đơn giản này, bạn đã đúng nhưng với những ví dụ phức tạp hơn, quá nhiều đại biểu trở nên vụng về.
Esben Skov Pedersen

@EsbenSkovPedersen: Ví dụ của bạn có một số giá trị, nhưng IMO chỉ nên được sử dụng trong trường hợp Innerkhông lồng nhau và internalkhông hoạt động (nghĩa là khi bạn không xử lý các hội đồng khác nhau). Khả năng đọc được từ các lớp lồng nhau làm cho nó ít thuận lợi hơn so với việc sử dụng internal(nếu có thể).
Flater

23

Thông thường, một lớp N lồng nhau được tạo bên trong lớp C bất cứ khi nào C cần sử dụng thứ gì đó bên trong mà không bao giờ được sử dụng (trực tiếp) bên ngoài C và vì bất kỳ lý do gì mà một thứ gì đó cần phải là một loại đối tượng mới thay vì một số đối tượng hiện có kiểu.

Tôi tin rằng điều này thường xảy ra khi thực hiện một phương thức trả về một đối tượng đang thực hiện một số giao diện và chúng tôi muốn giữ loại cụ thể của đối tượng đó bởi vì nó sẽ không hữu ích ở bất kỳ nơi nào khác.

Triển khai IEnumerable là một ví dụ tốt về điều này:

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

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

Đơn giản là không có lý do cho bất cứ ai bên ngoài BlobOfBusinessDatabiết hoặc quan tâm đến BusinessDatumEnumeratorloại bê tông , vì vậy chúng tôi cũng có thể giữ nó bên trong BlobOfBusinessData.

Đó không phải là một ví dụ "thực tiễn tốt nhất" về cách thực hiện IEnumerableđúng, chỉ là mức tối thiểu để có ý tưởng, vì vậy tôi đã bỏ qua những thứ như một IEnumerable.GetEnumerator()phương pháp rõ ràng .


6
Một ví dụ khác mà tôi sử dụng với các lập trình viên mới hơn là một Nodelớp trong a LinkedList. Bất cứ ai sử dụng LinkedListđều không quan tâm đến cách thức Nodetriển khai, miễn là họ có thể truy cập nội dung. Thực thể duy nhất quan tâm đến tất cả là LinkedListlớp học.
sư Xy

3

Vậy tại sao tạo một lớp lồng nhau?

Tôi có thể nghĩ ra vài lý do quan trọng:

1. Kích hoạt đóng gói

Nhiều lần các lớp lồng nhau là chi tiết thực hiện của lớp. Người dùng của lớp chính không cần phải quan tâm đến sự tồn tại của họ. Bạn sẽ có thể thay đổi chúng theo ý muốn mà không yêu cầu người dùng của lớp chính thay đổi mã của họ.

2. Tránh ô nhiễm tên

Bạn không nên thêm các loại, biến, hàm, v.v. trong một phạm vi trừ khi chúng phù hợp trong phạm vi đó. Điều này khác hoàn toàn so với đóng gói. Có thể hữu ích để hiển thị giao diện của loại lồng nhau nhưng vị trí thích hợp cho loại lồng vẫn là lớp chính. Trong vùng đất C ++, các kiểu lặp là một ví dụ như vậy. Tôi không có kinh nghiệm về C # đủ để cung cấp cho bạn các ví dụ riêng biệt trong đó.

Chúng ta hãy tạo một ví dụ đơn giản để minh họa tại sao việc di chuyển một lớp lồng vào cùng phạm vi với lớp chính là ô nhiễm tên. Giả sử bạn đang thực hiện một lớp danh sách liên kết. Thông thường, bạn sẽ sử dụng

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

Nếu bạn quyết định chuyển Nodelên cùng phạm vi LinkedList, bạn sẽ có

public class LinkedListNode
{
}

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

LinkedListNodedường như không hữu ích nếu không có LinkedListlớp học. Ngay cả khi LinkedListđược cung cấp một số chức năng truy xuất lại một LinkedListNodeđối tượng mà người dùng LinkedListcó thể sử dụng, nó vẫn LinkedListNodechỉ hữu ích khi LinkedListđược sử dụng. Vì lý do đó, làm cho lớp "nút" trở thành lớp ngang hàng LinkedListđang làm ô nhiễm phạm vi chứa.


0
  1. Tôi sử dụng các lớp lồng nhau công khai cho các lớp trợ giúp liên quan.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Sử dụng chúng cho các biến thể liên quan.

    // 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();

Người gọi có thể chọn phiên bản phù hợp cho vấn đề nào. Đôi khi một lớp không thể được xây dựng đầy đủ trong một lần và yêu cầu phiên bản có thể thay đổi. Điều này luôn đúng khi xử lý dữ liệu tuần hoàn. Mutables có thể được chuyển đổi thành chỉ đọc sau này.

  1. Tôi sử dụng chúng cho hồ sơ nội bộ

    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

Lớp lồng có thể được sử dụng bất cứ khi nào bạn muốn tạo nhiều hơn một lần phiên bản của lớp hoặc bất cứ khi nào bạn muốn làm cho loại đó có sẵn hơn.

Nested Class làm tăng các đóng gói cũng như nó sẽ dẫn đến mã dễ đọc và dễ bảo trì hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.