“Публичные” вложенные классы или нет
-
22-07-2019 - |
Вопрос
Предположим, у меня есть класс "Application".Для инициализации требуются определенные настройки в конструкторе.Давайте также предположим, что количество настроек настолько велико, что возникает необходимость поместить их в отдельный класс.
Сравните следующие две реализации этого сценария.
Реализация 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
}
}
На мой взгляд, второй подход гораздо предпочтительнее.Он более удобочитаем, потому что в нем сильно подчеркивается связь между двумя классами.Когда я пишу код для создания экземпляра класса приложения в любом месте, второй подход будет выглядеть красивее.
Теперь просто представьте, что сам класс Settings, в свою очередь, имел какой-то аналогично "связанный" класс, и этот класс, в свою очередь, тоже это делал.Пройдите только три таких уровня, и именование класса выйдет из-под контроля в случае "не вложенности".Однако, если вы гнездитесь, все по-прежнему остается элегантным.
Несмотря на вышесказанное, я читал, как люди говорили в StackOverflow, что вложенные классы оправданы только в том случае, если они не видны внешнему миру;это в том случае, если они используются только для внутренней реализации содержащего их класса.Часто цитируемое возражение заключается в раздувании размера исходного файла, содержащего класс, но partial classes - идеальное решение этой проблемы.
Мой вопрос в том, почему мы с опаской относимся к "общедоступному" использованию вложенных классов?Есть ли какие-либо другие аргументы против такого использования?
Решение
Я думаю, что все в порядке. В основном это шаблон компоновщика, и использование вложенных классов работает довольно хорошо. Это также позволяет разработчику получить доступ к закрытым членам внешнего класса, что может быть очень полезно. Например, у вас может быть метод Build для компоновщика, который вызывает приватный конструктор внешнего класса, который принимает экземпляр компоновщика:
public class Outer
{
private Outer(Builder builder)
{
// Copy stuff
}
public class Builder
{
public Outer Build()
{
return new Outer(this);
}
}
}
Это гарантирует, что только способ создания экземпляра внешнего класса - через конструктор.
Я очень часто использую этот шаблон в моем порте протокольных буферов C #.
Другие советы
Вы можете использовать пространства имен, чтобы связать вещи, которые ... связаны.
Например:
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
для обратитесь к Заполнение
.
И как ты будешь спать по ночам, зная это?
Возможно, вы захотите узнать, что должна сделать Microsoft скажи по теме. В основном это вопрос стиля, я бы сказал.
Другой практический пример, который у меня есть для правильного использования открытых вложенных классов, - это шаблон MVC, когда я использую модель представления со свойством 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 является публичным.
В основном я использую вложенные классы для точной настройки доступа к вложенному классу и / или контейнеру.
Следует помнить одну вещь: определение вложенного класса в основном является членом класса и будет иметь доступ ко всем закрытым переменным контейнера.
Вы также можете использовать это для управления использованием определенного класса.
Пример:
public abstract class Outer
{
protected class Inner
{
}
}
Теперь, в этом случае, пользователь (вашего класса) может получить доступ только к Внутреннему классу, если он реализует Внешний.