"Открытые" вложенные классы или нет
Предположим, что у меня есть класс "Приложение". Для инициализации он принимает определенные настройки в конструкторе. Предположим также, что количество настроек настолько велико, что оно вынуждает их размещать в своем классе.
Сравните следующие две реализации этого сценария.
Реализация 1:
class Application
{
Application(ApplicationSettings settings)
{
//Do initialisation here
}
}
class ApplicationSettings
{
//Settings related methods and properties here
}
Реализация 2:
class Application
{
Application(Application.Settings settings)
{
//Do initialisation here
}
class Settings
{
//Settings related methods and properties here
}
}
Для меня второй подход очень предпочтительнее. Это более читаемо, потому что оно сильно подчеркивает связь между двумя классами. Когда я пишу код для создания экземпляра класса Application в любом месте, второй подход будет выглядеть красивее.
Теперь представьте себе, что у самого класса настроек в свою очередь был некоторый аналогичный "родственный" класс, и этот класс в свою очередь тоже сделал это. Перейдите только к трем таким уровням, и название класса выйдет из-под контроля в "не-вложенном" случае. Однако, если вы гнездились, все по-прежнему остается изящным.
Несмотря на вышеизложенное, я читал людей, говорящих в StackOverflow, что вложенные классы оправданы, только если они не видны внешнему миру; то есть, если они используются только для внутренней реализации содержащего класса. Часто цитируемое возражение вызывает раздувание размера содержащего исходный файл класса, но частичные классы являются идеальным решением для этой проблемы.
Мой вопрос: почему мы опасаемся "публично выставленного" использования вложенных классов? Существуют ли другие аргументы против такого использования?
Ответы
Ответ 1
Думаю, все в порядке. Это в основном шаблон построителя, и использование вложенных классов работает очень хорошо. Он также позволяет строителю получить доступ к закрытым членам внешнего класса, что может быть очень полезным. Например, вы можете иметь метод Build в построителе, который вызывает частный конструктор внешнего класса, который принимает экземпляр компоновщика:
public class Outer
{
private Outer(Builder builder)
{
// Copy stuff
}
public class Builder
{
public Outer Build()
{
return new Outer(this);
}
}
}
Это гарантирует, что единственный способ создания экземпляра внешнего класса - через конструктор.
Я использую шаблон, очень похожий на мой порт С# для протокольных буферов.
Ответ 2
Вы можете использовать пространства имен, чтобы связать вещи, которые... связаны.
Например:
namespace Diner
{
public class Sandwich
{
public Sandwich(Filling filling) { }
}
public class Filling { }
}
Преимущество этого в использовании классов, как если бы они были пространствами имен, заключается в том, что вы можете использовать using
на вызывающей стороне, чтобы сокращать вещи:
using Diner;
...
var sandwich = new Sandwich(new Filling());
Если вы используете класс Sandwich
, как если бы это было пространство имен для Filling
, вы должны использовать полное имя Sandwich.Filling
для ссылки на Filling
.
И как вы собираетесь спать по ночам, зная это?
Ответ 3
Возможно, вы захотите проверить, что Microsoft должна сказать по этой теме. В основном это вопрос стиля, который я бы сказал.
Ответ 4
В основном я использую вложенные классы для точной настройки доступа к вложенному и/или контейнерному классу.
Следует помнить, что определение вложенного класса в основном является членом класса и будет иметь доступ ко всем личным переменным контейнера.
Вы также можете использовать это для управления использованием определенного класса.
Пример:
public abstract class Outer
{
protected class Inner
{
}
}
Теперь в этом случае пользователь (вашего класса) может получить доступ только к внутреннему классу, если он реализует Outer.
Ответ 5
Еще один практический пример, который я применил для использования публичных вложенных классов, находится в шаблоне MVC, когда я использую viewmodel с свойством IEnumerable. например:
public class OrderViewModel
{
public int OrderId{ get; set; }
public IEnumerable<Product> Products{ get; set; }
public class Product {
public string ProductName{ get; set; }
public decimal ProductPrice{ get; set; }
}
}
Я использую его, потому что я не хочу, чтобы класс Product
был повторно использован снаружи, потому что он настроен только для этой конкретной модели просмотра, которая ее содержит. Но я не могу сделать это частным, потому что свойство Products является общедоступным.