c # Публичные вложенные классы или лучший вариант? - PullRequest
14 голосов
/ 02 ноября 2011

У меня есть схема управления, которая имеет несколько настроек и может иметь любое количество датчиков, подключенных к ней (каждый со своим набором настроек). Эти датчики могут использоваться только с цепью управления. Я думал об использовании вложенных классов следующим образом:

public class ControlCircuitLib
{
    // Fields.
    private Settings controllerSettings;
    private List<Sensor> attachedSensors;

    // Properties.
    public Settings ControllerSettings
    { get { return this.controllerSettings; } }

    public List<Sensor> AttachedSensors
    { get { return this.attachedSensors; } }

    // Constructors, methods, etc.
    ...

    // Nested classes.
    public class Settings
    {
       // Fields.
       private ControlCircuitLib controllerCircuit;
       private SerialPort controllerSerialPort;
       private int activeOutputs;
       ... (many, many more settings)

       // Properties.
       public int ActiveOutputs
       { get { return this.activeOutputs; } }
       ... (the other Get properties for the settings)

       // Methods.
       ... (method to set the circuit properties though serial port)        
    }

    public class Sensor
    {
       // Enumerations.
       public enum MeasurementTypes { Displacement, Velocity, Acceleration };

       // Fields.
       private ControlCircuitLib controllerCircuit;
       private string sensorName;
       private MeasurementTypes measurementType;
       private double requiredInputVoltage;
       ... (many, many more settings)

       // Properties.
       public string SensorName {...}
       ... (Get properties)

       // Methods.
       ... (methods to set the sensor settings while attached to the control circuit)
    }
}

Я читал, что публичные вложенные классы - это "нет-нет", но есть исключения. Эта структура в порядке или есть лучший вариант?

Спасибо!

EDIT

Ниже приведена грубая иерархия схемы управления, для которой я пытаюсь написать библиотечный класс; Я использовал форматирование кода, чтобы предотвратить перенос текста.

Control Circuit (com. via serial port) -> Attached Sensors (up to 10) -> Sensor Settings (approx. 10 settings per sensor)
                                          Basic Controller Settings (approx. 20 settings)
                                          Output Settings (approx. 30 settings)
                                          Common Settings (approx. 30 settings)
                                          Environment Settings (approx. 10 settings)

Все настройки устанавливаются через контроллер, но я бы хотел организовать библиотеку, а не просто объединять все ~ 100 методов, свойств и настроек в одном классе Controller. Было бы очень признательно, если бы кто-то мог предложить короткий пример с описанием структуры, которую он будет использовать. Спасибо!

Ответы [ 8 ]

22 голосов
/ 02 ноября 2011

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

Если первый,тогда вам не следует публиковать подробности частной реализации.Сделайте их закрытыми, если они являются деталями реализации класса.

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

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

7 голосов
/ 02 ноября 2011

У меня нет слишком особых проблем с общедоступными вложенными классами (в общем, я не фанат догматических правил), но рассматривали ли вы вместо этого помещение всех этих типов в свое собственное пространство имен? Это более распространенный способ группировки классов.

РЕДАКТИРОВАТЬ: Просто чтобы уточнить, я бы очень редко использовал общедоступные вложенные классы, и я, вероятно, не стал бы использовать их здесь, но я бы не стал полностью отказываться от них. Существует множество примеров общедоступных вложенных типов в фреймворке (например, List<T>.Enumerator) - без сомнения, в каждом случае дизайнеры считали «запах» использования вложенного класса и считали его меньшим запахом, чем продвижение типа быть высокоуровневым или создать новое пространство имен для задействованных типов.

4 голосов
/ 03 ноября 2011

Из вашего комментария к ответу Эрика:

Эти датчики могут использоваться ТОЛЬКО с определенной цепью

Этот тип отношений обычно известен как зависимость .Конструктор Sensor должен принимать ControlCircuit в качестве параметра.Вложенные классы не не передают эту связь.

, и вы не можете получить / установить какие-либо настройки датчика, не пройдя через схему контроллера;

Я думаю, это означает, что все свойства Sensor будут делегировать (вызывать) или каким-либо образом сообщать (запускать событие) ControlCircuit, когда они используются.Или у вас будет какой-то внутренний интерфейс с датчиком, который используется только схемой управления, что делает Sensor непрозрачный класс для внешнего мира.В этом случае Sensor - это просто деталь реализации, которая может быть вложенной или частной (нет необходимости «сохранять» экземпляр датчика, если вы ничего не можете с ним сделать).

Кроме того, я даже не хочу показывать конструктор датчика (у контроллера будет метод для этого)

Тот факт, что конструктор Sensor теперь принимает схему управления, достаточнонамек на то, что зависит от того, что вы можете оставить конструктору public.Вы также можете сделать это internal.

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

3 голосов
/ 02 ноября 2011

Я вообще не согласен с Эриком по этому поводу.

Обычно я думаю о том, как часто конечный пользователь должен использовать имя типа ControlCircuitLib.Sensor.Если это «почти никогда, но тип должен быть публичным, чтобы можно было что-то делать», тогда переходите к внутренним типам.Для чего-либо еще используйте отдельный тип.

Например,

public class Frobber {
    public readonly FrobType Standard = ...;
    public readonly FrobType Advanced = ...;

    public void Frob(FrobType type) { ... }

    public class FrobType { ... }
}

В этом примере FrobType действует только как непрозрачная «вещь».Только Frobber должен знать, что это на самом деле, хотя должна быть возможность передать его за пределы этого класса.Однако такого рода примеры довольно редки;чаще всего вам следует избегать вложенных общедоступных классов.

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

3 голосов
/ 02 ноября 2011

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

1 голос
/ 28 августа 2014

Эта структура кажется мне совершенно разумной.До сегодняшнего дня я не знал, что Microsoft посоветовал не делать этого, но я все еще не знаю, почему они посоветовали так.

Я использовал эту структуру в ситуации, когда только вложенный класссуществует для поддержки содержащего класса (т.е. это часть его реализации), но другие классы должны иметь возможность видеть его, чтобы взаимодействовать с содержащим классом (т.е. это часть API класса).

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

В настоящее время мне не нравятся результаты,У меня есть класс с именем BasicColumn, который существует только для представления столбца в классе с именем Grid.Ранее этот класс всегда назывался Grid.BasicColumn, и это было здорово.Это именно то, как я хочу, чтобы это упоминалось.Теперь, когда Grid и BasicColumn находятся в пространстве имен Grids, его просто называют BasicColumn с «использованием Grids» в верхней части файла.Ничто не указывает на его особую связь с Grid, если только я не хочу печатать все пространство имен (в котором есть несколько префиксов перед Grid, который я оставил для простоты).

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

1 голос
/ 02 ноября 2011

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

0 голосов
/ 16 августа 2016

Хотя я чувствую, что ответ Эрика верен, важно понимать, что он не совсем соответствует ситуации, в которой вы находитесь.

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

В этих случаяхчто вы можете сделать, это использовать интерфейсы.Вложенные классы не обязательно должны быть открытыми, поскольку они на самом деле являются внутренними деталями класса, в который они вложены, но подмножество функций (интерфейс) должно быть общедоступным и может быть реализовано классом.

Это позволяет управлять построением внутренних структур классом, в который они вложены, и в то же время обеспечивает прямой доступ к типу из пространства имен для внешних ссылок.(Вызывающая сторона будет использовать SomeNamespace.IChildApi в качестве имени, а не SomeNamespace.NestingClass.NestedClass.SecondNestedClass.ThirdNestedClass и т. Д.)

...