Инкапсуляция в эпоху каркасов - PullRequest
7 голосов
/ 18 февраля 2009

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

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

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

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

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

Ответы [ 3 ]

3 голосов
/ 18 февраля 2009

Я не согласен с вашим выводом. Есть много хороших способов инкапсуляции в 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] атрибутов или эквивалентов в правильные поля.

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

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

1 голос
/ 18 февраля 2009

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

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

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

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

0 голосов
/ 18 февраля 2009

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

...