Когда и зачем использовать вложенные классы?


30

Используя объектно-ориентированное программирование, у нас есть возможность создать класс внутри класса (вложенный класс), но я никогда не создавал вложенный класс за 4 года своего опыта программирования.
Для чего нужны вложенные классы?

Я знаю, что класс может быть помечен как закрытый, если он вложенный, и что мы можем получить доступ ко всем закрытым членам этого класса из содержащего класса. Мы могли бы просто поместить переменные как приватные в сам содержащий класс.
Так зачем создавать вложенный класс?

В каких сценариях должны использоваться вложенные классы или они более мощные с точки зрения использования по сравнению с другими методами?


1
У вас есть несколько хороших ответов, а иногда у меня просто есть рабочий класс или стойка, которые мне нужны только внутри класса.
Папараццо

Ответы:


19

Главной особенностью вложенных классов является то, что они могут получить доступ к закрытым членам внешнего класса, имея при этом всю мощь самого класса. Также они могут быть приватными, что позволяет при некоторых обстоятельствах выполнять довольно мощную инкапсуляцию:

Здесь мы полностью блокируем установщик до фабрики, так как класс является частным, ни один потребитель не может уменьшить его и получить доступ к установщику, и мы можем полностью контролировать то, что разрешено.

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

В большинстве случаев я ожидаю, что делегаты будут более чистым способом делать то, что вы показываете во втором примере
Бен Ааронсон,

@BenAaronson, как бы вы реализовали случайный интерфейс с использованием делегатов?
Эсбен Сков Педерсен

@EsbenSkovPedersen Ну, для вашего примера, вместо того, чтобы передать экземпляр Outer, вы должны передать Func<int>, который будет просто() => _example
Бен Ааронсон

@BenAaronson в этом чрезвычайно простом случае вы правы, но для более сложных примеров слишком много делегатов становится неуклюжим.
Эсбен Сков Педерсен

@EsbenSkovPedersen: Ваш пример имеет некоторые достоинства, но IMO следует использовать только в тех случаях, когда создание Innerне вложено и internalне работает (т.е. когда вы не имеете дело с различными сборками). Хит читаемости от классов вложенности делает его менее благоприятным, чем использование internal(где это возможно).
Флатер

23

Как правило, вложенный класс N создается внутри класса C всякий раз, когда C нужно использовать что-то внутренне, что никогда не должно (непосредственно) использоваться вне C, и по любой причине, что что-то должно быть объектом нового типа, а не каким-либо существующим тип.

Я считаю, что чаще всего это происходит при реализации метода, который возвращает объект, реализующий некоторый интерфейс, и мы хотим скрыть конкретный тип этого объекта, потому что он больше нигде не будет полезен.

Реализация IEnumerable является хорошим примером этого:

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

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

У кого-то за пределами просто нет причин BlobOfBusinessDataзнать или заботиться о конкретном BusinessDatumEnumeratorтипе, поэтому мы могли бы оставить его внутри BlobOfBusinessData.

Это не предназначалось для того, чтобы быть примером «наилучшей практики» для 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

Вложенный класс можно использовать всякий раз, когда вы хотите создать более одного экземпляра класса или когда вы хотите сделать этот тип более доступным.

Вложенный класс увеличивает инкапсуляцию, а также приведет к более удобочитаемому и поддерживаемому коду.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.