Инкапсуляция в эпоху фреймворков

На моей старой работе на C ++ мы всегда очень заботились о том, чтобы инкапсулировать переменные-члены и отображать их как свойства только в случае крайней необходимости. У нас были бы действительно конкретные конструкторы, которые проверяли, что вы полностью сконструировали объект перед его использованием.

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

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

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

РЕДАКТИРОВАТЬ: Я знаю, что вы все еще можете тщательно инкапсулировать члены, но мне просто кажется, что когда вы пытаетесь запустить некоторые классы, вам нужно либо сесть и тщательно подумать о том, как инкапсулировать каждый член, либо просто раскрыть его как свойство, и беспокоиться о том, как он будет инициализирован позже. Просто кажется, что в наши дни самый простой подход - раскрыть вещи как свойства, и не будьте так осторожны. Возможно, я просто ошибаюсь, но это просто мой опыт, особенно с новыми функциями языка C #.


person Andy White    schedule 18.02.2009    source источник
comment
Не позволяйте никому вводить вас в заблуждение, эти инициализаторы объектов - всего лишь синтаксический сахар.   -  person BobbyShaftoe    schedule 18.02.2009
comment
Я знаю, что инициализаторы объектов на самом деле не конструкторы, но я не об этом. Они позволяют создавать объект с помощью конструктора по умолчанию и вручную задавать свойства.   -  person Andy White    schedule 18.02.2009
comment
Не совсем уверен, в чем разница между ними, и просто набираю каждое выражение для установки каждого свойства. Но ладно.   -  person BobbyShaftoe    schedule 19.02.2009
comment
Не позволяйте никому вводить вас в заблуждение, я понимаю, что делает инициализатор объекта, я не это пытаюсь обсуждать. Если вы чувствуете необходимость указать на очевидные вещи, можете ли вы в следующий раз опустить снисходительную чушь.   -  person Andy White    schedule 19.02.2009


Ответы (3)


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

Возьмем объектно-реляционные структуры; большинство из них позволяют указать, как они собираются гидратировать сущности; NHibernate, например, позволяет вам так сказать access="property" или access="field.camelcase" и тому подобное. Это позволяет вам инкапсулировать ваши свойства.

Внедрение зависимостей работает с другими типами, которые у вас есть, в основном с теми, которые не являются сущностями, хотя вы можете комбинировать AOP + ORM + IOC некоторыми очень хорошими способами, чтобы улучшить состояние этих вещей. IoC часто используется из уровней над объектами вашего домена, если вы создаете приложение, управляемое данными, что, я думаю, и есть, поскольку вы говорите об ORM.

Они («они» являются службами приложения и домена и другими внутренними классами программы) раскрывают свои зависимости, но на самом деле могут быть инкапсулированы и протестированы в даже лучшей изоляции, чем раньше, поскольку парадигмы проектирования по контракту / проектирования по интерфейсу который вы часто используете при имитации зависимостей в тестировании на основе имитаций (в сочетании с IoC), это приведет вас к «семантике» класса как компонента. Я имею в виду: каждый класс, построенный с использованием вышеуказанного, будет лучше инкапсулирован.

Обновлено для urig: это верно как для раскрытия конкретных зависимостей, так и для раскрытия интерфейсов. Сначала об интерфейсах: выше я намекал, что службы и другие классы приложений, которые имеют зависимости, могут с ООП зависеть от контрактов / интерфейсов, а не от конкретных реализаций. В C / C ++ и более старых языках не было интерфейса, и абстрактные классы могли только зайти так далеко. Интерфейсы позволяют связывать разные экземпляры среды выполнения с одним и тем же интерфейсом, не беспокоясь об утечке внутреннего состояния, от чего вы пытаетесь избежать при абстрагировании и инкапсуляции. С абстрактными классами вы по-прежнему можете предоставить реализацию класса, просто вы не можете создать его экземпляр, но наследники по-прежнему должны знать об инвариантах в вашей реализации, и это может испортить состояние.

Во-вторых, о конкретных классах как свойствах: вы должны опасаться, какие типы типов вы представляете как свойства. Скажем, у вас есть список в вашем экземпляре; тогда не выставляйте IList как свойство; это, вероятно, приведет к утечке, и вы не можете гарантировать, что потребители интерфейса не добавят и не удалят вещи, от которых вы зависите; вместо этого откройте что-то вроде IEnumerable и верните копию списка или, что еще лучше, сделайте это как метод: public IEnumerable MyCollection {get {return _List.Enum (); }} и вы можете быть на 100% уверены, что получите как производительность, так и инкапсуляцию. Никто не может добавить или удалить этот IEnumerable, и вам все равно не придется выполнять дорогостоящее копирование массива. Соответствующий вспомогательный метод:

static class Ext {
    public static IEnumerable<T> Enum<T>(this IEnumerable<T> inner) {
        foreach (var item in inner) yield return item;
    }
}

Таким образом, хотя вы не можете получить 100% инкапсуляцию, скажем, при создании перегруженных операторов / методов равенства, вы можете приблизиться к своим общедоступным интерфейсам.

Вы также можете использовать новые функции .Net 4.0, построенные на Spec #, для проверки контрактов, о которых я говорил выше.

Сериализация будет всегда и существует уже давно. Раньше, до интернет-области, он использовался для сохранения графа вашего объекта на диск для последующего извлечения, теперь он используется в веб-сервисах, в семантике копирования и при передаче данных, например, в браузер. Это не обязательно нарушит инкапсуляцию, если вы поместите несколько атрибутов [NonSerialized] или их эквиваленты в правильные поля.

Инициализаторы объектов - это не то же самое, что конструкторы, они просто способ свернуть несколько строк кода. Значения / экземпляры в {} не будут назначаться до тех пор, пока все ваши конструкторы не будут запущены, поэтому в принципе это то же самое, что и не использовать инициализаторы объектов.

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

person Henrik    schedule 18.02.2009
comment
Привет, Хенрик! Можете ли вы привести пример того, как они раскрывают свои зависимости, но на самом деле их можно инкапсулировать и протестировать в еще лучшей изоляции, чем раньше? Если класс раскрывает свои зависимости в общедоступных свойствах, как это может не нарушать инкапсуляцию? Спасибо! уриг - person urig; 18.06.2009
comment
Возвращаясь, глядя на это, я вижу, что не совсем понял, что имел в виду. - person Henrik; 22.06.2009

Частные участники по-прежнему невероятно важны. Контроль доступа к внутренним данным объекта - это всегда хорошо, и его нельзя игнорировать.

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

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

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

person Robert P    schedule 18.02.2009

Я думаю, что инкапсуляция по-прежнему важна, она помогает больше в библиотеках, чем что-либо, imho. Вы можете создать библиотеку, которая выполняет X, но вам не нужно, чтобы все знали, как был создан X. И если вы хотите создать его более конкретно, чтобы скрыть способ создания X. То, как я узнал об инкапсуляции, я также помню, что вы всегда должны определять свои переменные как частные, чтобы защитить их от атаки данных. Для защиты от взлома вашего кода хакером и доступа к переменным, которые они не должны использовать.

person Suroot    schedule 18.02.2009