По каким причинам можно использовать вложенные классы?
В этом ответе на стекопоток комментатор упомянул, что "частные вложенные классы" могут быть весьма полезны, поэтому я читал о них в статьях, подобных этой, которые, как правило, объясняют, как вложенные классы функционируют технически, но не почему вы их используете.
Я предполагаю, что я бы использовал частные вложенные классы для маленьких вспомогательных классов, принадлежащих большему классу, но часто мне понадобится вспомогательный класс из другого класса, и поэтому мне просто нужно приложить дополнительные усилия, чтобы (1) сделать вложенный класс не -nested или (2) сделать его общедоступным, а затем получить к нему доступ с префиксом внешнего класса, что, как представляется, является дополнительной работой без какого-либо дополнительного значения для того, чтобы иметь вложенный класс в первую очередь. Следовательно, в общем, я действительно не вижу варианта использования для вложенных классов, кроме как, возможно, для того, чтобы классы были немного более организованы в группы, но я также иду против ясности одного класса на файл, которой я пришел насладиться,
Каким образом вы используете вложенные классы, чтобы сделать ваш код более управляемым, читабельным и эффективным?
14 ответов
Вы ответили на свой вопрос. Используйте вложенные классы, когда вам нужен вспомогательный класс, который не имеет смысла вне класса; особенно когда вложенный класс может использовать частные детали реализации внешнего класса.
Ваш аргумент о том, что вложенные классы бесполезны, также является аргументом о том, что закрытые методы бесполезны: закрытый метод может быть полезен вне класса, и поэтому вам придется сделать его внутренним. Внутренний метод может быть полезен вне сборки, и поэтому вы бы сделали его общедоступным. Поэтому все методы должны быть публичными. Если вы считаете, что это плохой аргумент, то чем отличается то, что вы используете один и тот же аргумент для классов вместо методов?
Я делаю вложенные классы все время, потому что я часто нахожусь в положении, необходимом для инкапсуляции функциональности в помощнике, который не имеет смысла вне класса, и может использовать частные детали реализации внешнего класса. Например, я пишу компиляторы. Недавно я написал класс SemanticAnalyzer, который выполняет семантический анализ деревьев разбора. Один из его вложенных классов - LocalScopeBuilder. При каких обстоятельствах мне нужно построить локальную область, когда я не анализирую семантику дерева разбора? Никогда. Этот класс целиком является деталью реализации семантического анализатора. Я планирую добавить больше вложенных классов с именами, такими как NullableArithmeticAnalyzer и OverloadResolutionAnalyzer, которые также бесполезны вне класса, но я хочу инкапсулировать правила языка в этих конкретных классах.
Люди также используют вложенные классы для создания таких вещей, как итераторы или компараторы - вещи, которые не имеют смысла вне класса и доступны через хорошо известный интерфейс.
Шаблон, который я использую довольно часто, состоит в том, чтобы иметь частные вложенные классы, которые расширяют их внешний класс:
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 { ... }
и так далее. Вложенные классы очень хорошо работают с фабричным шаблоном. Здесь BankAccount является фабрикой для различных типов банковских счетов, каждый из которых может использовать частные детали реализации BankAccount. Но никакая третья сторона не может создать свой собственный тип EvilBankAccount, который расширяет BankAccount.
Возврат интерфейса вызывающей стороне, чью реализацию вы хотите скрыть.
public class Outer
{
private class Inner : IEnumerable<Foo>
{
/* Presumably this class contains some functionality which Outer needs
* to access, but which shouldn't be visible to callers
*/
}
public IEnumerable<Foo> GetFoos()
{
return new Inner();
}
}
Частные вспомогательные классы - хороший пример.
Например, объекты состояния для фоновых потоков. Нет веских причин выставлять эти типы. Определение их как закрытых вложенных типов кажется довольно чистым способом решения проблемы.
Я нашел несколько случаев, когда они были очень полезны:
Управление сложным частным состоянием, таким как InterpolationTriangle, используемый классом Interpolator. Пользователь Интерполятора не должен знать, что он реализован с использованием триангуляции Делоне, и, конечно, не должен знать о треугольниках, поэтому структура данных является частным вложенным классом.
Как уже упоминали другие, вы можете предоставлять данные, используемые классом, с помощью интерфейса, не раскрывая полную реализацию класса. Вложенные классы также могут получать доступ к закрытому состоянию внешнего класса, что позволяет писать тесно связанный код, не раскрывая эту плотную связь публично (или даже внутри остальной части сборки).
Я сталкивался с несколькими случаями, когда фреймворк ожидает, что класс наследуется от некоторого базового класса (например, DependencyObject в WPF), но вы хотите, чтобы ваш класс наследовал от другой базы. Можно взаимодействовать с платформой с помощью частного вложенного класса, который происходит от базового класса фреймворка. Поскольку вложенный класс может обращаться к приватному состоянию (вы просто передаете ему родительское "this" при его создании), вы можете в основном использовать его для реализации множественного наследования бедного человека посредством композиции.
Я использую их, когда два связанных значения (как в хеш-таблице) недостаточно для внутреннего использования, но достаточно для внешнего использования. Затем я создаю вложенный класс со свойствами, которые мне нужно сохранить, и выставляю только некоторые из них с помощью методов.
Я думаю, что это имеет смысл, потому что, если никто не собирается использовать его, зачем создавать для него внешний класс? Это просто не имеет смысла.
Что касается одного класса на файл, вы можете создать частичные классы с partial
Ключевое слово, которое я обычно делаю.
Одним из убедительных примеров, с которыми я столкнулся в последнее время, является Node
класс многих структур данных. Quadtree
Например, ему нужно знать, как он хранит данные в своих узлах, но никакой другой части вашего кода это не должно волновать.
Я думаю, что другие хорошо рассмотрели варианты использования для публичных и частных вложенных классов.
Один момент, который я не видел, был ответом на вашу озабоченность по поводу одного класса на файл. Вы можете решить эту проблему, сделав внешний класс частичным, и переместить определение внутреннего класса в отдельный файл.
OuterClass.cs:
namespace MyNameSpace
{
public partial class OuterClass
{
// main class members here
// can use inner class
}
}
OuterClass.Inner.cs:
namespace MyNameSpace
{
public partial class OuterClass
{
private class Inner
{
// inner class members here
}
}
}
Вы даже можете использовать вложенность элементов Visual Studio, чтобы сделать OuterClass.Inner.cs дочерним по отношению к OuterClass.cs, чтобы не загромождать проводник вашего решения.
"частные вложенные классы" могут быть весьма полезны
Обратите внимание на частное в этом предложении. Открытые вложенные типы не рекомендуются.
И, как и большинство ответов, уже отмечаем: каждый класс представляет собой небольшой программный проект. Иногда становится желательно заручиться вспомогательными классами. Но это не то, к чему нужно стремиться, вы все равно не хотите, чтобы занятия становились слишком большими или слишком сложными.
Один из наиболее распространенных шаблонов, в которых используется этот метод, - это сценарии, в которых класс возвращает интерфейс или тип базового класса из одного из своих свойств или методов, но конкретный тип является закрытым вложенным классом. Рассмотрим следующий пример.
public class MyCollection : IEnumerable
{
public IEnumerator GetEnumerator()
{
return new MyEnumerator();
}
private class MyEnumerator
{
}
}
Я обычно делаю это, когда мне нужна комбинация SRP (принципала единой ответственности) в определенных ситуациях.
"Хорошо, если SRP - ваша цель, почему бы не разделить их на разные классы? " Вы будете делать это 80% времени, но как насчет ситуаций, когда создаваемые вами классы бесполезны для внешнего мира? Вам не нужны классы, которые только вы будете использовать, чтобы загромождать API вашей сборки.
"Ну, разве это не то, что internal
для?" Конечно. Примерно для 80% этих случаев. Но как насчет внутренних классов, которые должны получить доступ или изменить состояние открытых классов? Например, тот класс, который был разбит на один или несколько внутренних классов, чтобы удовлетворить вашу серию SRP Вы должны были бы отметить все методы и свойства для использования этими internal
классы как internal
также.
"Что в этом плохого?" Ничего такого. Примерно для 80% этих случаев. Конечно, теперь вы загромождаете внутренний интерфейс ваших классов методами / свойствами, которые используются только для тех классов, которые вы создали ранее. И теперь вам нужно беспокоиться о том, что другие люди в вашей команде, пишущие внутренний код, не испортят ваше состояние, используя эти методы способами, которых вы не ожидали.
Внутренние классы могут изменять состояние любого экземпляра типа, в котором они определены. Таким образом, без добавления членов к определению вашего типа, ваши внутренние классы могут работать с ними по мере необходимости. Что примерно в 14 случаях из 100 будет лучшим выбором для поддержания чистоты ваших типов, надежного / поддерживаемого кода и исключительных обязанностей.
В качестве примера они действительно хороши для реализации шаблона синглтона.
У меня есть пара мест, где я использую их, чтобы также "добавить" ценность. У меня есть комбинированный список с множественным выбором, где мой внутренний класс хранит состояние флажка и элемента данных. не нужно, чтобы мир знал о / использовал этот внутренний класс.
Вопрос помечен C#, поэтому я не уверен, что это представляет интерес, но в COM вы можете использовать внутренние классы для реализации интерфейсов, когда класс C++ реализует несколько интерфейсов COM... по сути, вы используете его для композиции, а не множественного наследования.
Кроме того, в MFC и, возможно, в других технологиях вам может потребоваться, чтобы ваш элемент управления / диалог имел целевой класс удаления, что не имеет большого смысла, кроме как для вложенного класса.
Частные анонимные вложенные классы необходимы для обработчиков событий в графическом интерфейсе.
Если какой-то класс не является частью API, экспортируемого другим классом, он должен быть закрытым. В противном случае вы выставляете больше, чем намереваетесь. "Ошибка на миллион долларов" была тому примером. Большинство программистов слишком слабы по этому поводу.
Питер
Если объекту необходимо вернуть некоторую абстрактную информацию о своем состоянии, может пригодиться закрытый вложенный класс. Например, если Fnord поддерживает методы "сохранить контекст" и "восстановить контекст", может быть полезно, чтобы функция "сохранить контекст" возвращала объект типа Fnord.SavedContext. Правила доступа типа не всегда самые полезные; например, кажется трудным позволить Fnord получать доступ к свойствам и методам Fnord.SavedContext, не делая такие свойства и методы видимыми для посторонних. С другой стороны, можно заставить Fnord.CreateSaveContext просто создать новый Fnord.SaveContext с Fnord в качестве параметра (поскольку Fnord.SaveContext может обращаться к внутренним объектам Fnord), а Fnord.LoadContextFrom() может вызывать Fnord.SaveContext.RestoreContext. ().