Как отражение может не привести к запахам кода? - PullRequest
63 голосов
/ 04 февраля 2010

Я из языков низкого уровня - C ++ - это самый высокий уровень, на котором я программирую.

Недавно я наткнулся на Reflection, и я просто не могу понять, как его можно использовать без запахов кода.

Идея проверки класса / метода / функции во время выполнения, на мой взгляд, указывает на недостаток в дизайне - я думаю, что большинство проблем, которые Reflection (пытается) решить, может быть использовано либо с полиморфизмом, либо с правильным использованием наследования. 1005 *

Я не прав? Я неправильно понимаю концепцию и полезность Reflection?

Я ищу хорошее объяснение того, когда использовать Reflection, когда другие решения не удастся или будут слишком громоздкими для реализации, а также когда НЕ использовать.

Пожалуйста, просветите этот низкоуровневый смазочный материал.

Ответы [ 18 ]

90 голосов
/ 04 февраля 2010

Отражение чаще всего используется для обхода статической системы типов, однако также имеет несколько интересных случаев использования:

Давайте напишем ORM!

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

// used to hook into the ORMs innards
public class ActiveRecordBase
{
    public void Save();
}

public class User : ActiveRecordBase
{
    public int ID { get; set; }
    public string UserName { get; set; }
    // ...   
}

Как вы думаете, метод Save() написан? Ну, в большинстве ORM метод Save не знает, какие поля находятся в производных классах, но он может получить к ним доступ, используя отражение.

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

Столбики!

Rhino Mocks - это насмешливый каркас. Вы передаете тип интерфейса в метод, и за кулисами фреймворк динамически создает и создает экземпляр фиктивного объекта, реализующего интерфейс.

Конечно, программист мог бы написать шаблонный код для фиктивного объекта вручную, но зачем ей это, если фреймворк сделает это для нее?

Metadata!

Мы можем декорировать методы атрибутами (метаданными), которые могут служить различным целям:

[FilePermission(Context.AllAccess)]    // writes things to a file
[Logging(LogMethod.None)]              // logger doesn't log this method
[MethodAccessSecurity(Role="Admin")]   // user must be in "Admin" group to invoke method
[Validation(ValidationType.NotNull, "reportName")] // throws exception if reportName is null
public void RunDailyReports(string reportName) { ... }

Вам нужно подумать над методом, чтобы проверить атрибуты. Большинство AOP-структур для .NET используют атрибуты для внедрения политики.

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

Давайте создадим структуру зависимостей!

Многие контейнеры IoC требуют некоторой степени отражения для правильной работы. Например:

public class FileValidator
{
    public FileValidator(ILogger logger) { ... }
}

// client code
var validator = IoC.Resolve<FileValidator>();

Наш контейнер IoC создаст экземпляр валидатора файла и передаст соответствующую реализацию ILogger в конструктор. Какая реализация? Это зависит от того, как это реализовано.

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

Если мы не знаем реализацию во время компиляции, не существует безопасного типа для создания экземпляра класса на основе его имени.

Позднее связывание / утка

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

public static void Log(string msg, object state) { ... }

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

Некоторые языки, такие как OCaml и Scala, поддерживают статически проверенную типизацию утки (называемую структурная типизация ), но иногда вы просто не обладаете знанием интерфейса объектов во время компиляции.

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

Время от времени обход системы типов позволяет значительно реорганизовать ваш код по сравнению со статическими типами, что приводит к немного более чистому коду (желательно скрытому за дружественным для программиста API :)). Многие современные статические языки принимают золотое правило «статическая типизация, где это возможно, динамическая типизация, где это необходимо», позволяя пользователям переключаться между статическим и динамическим кодом.

11 голосов
/ 04 февраля 2010

Такие проекты, как hibernate (отображение O / R) и StructureMap (внедрение зависимостей) были бы невозможны без Reflection.Как можно решить их одним полиморфизмом?

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

Отражение особенно полезно для картографирования задач.Идея соглашения по коду становится все более популярной, и для этого нужен какой-то тип Reflection.

В .NET 3.5+ у вас есть альтернатива - использование выражениядеревья.Они строго типизированы, и многие проблемы, которые были классически решены с помощью Reflection, были повторно реализованы с использованием лямбда-выражений и деревьев выражений (см. Свободный NHibernate , Ninject ).Но имейте в виду, что не каждый язык поддерживает такие виды конструкций;когда они недоступны, вы в основном застряли с Reflection.

В каком-то смысле (и я надеюсь, что я не слишком треплюсь с этим), Reflection очень часто используется в качестве обходного пути / хака.в объектно-ориентированных языках для функций, которые предоставляются бесплатно на функциональных языках.По мере того, как функциональные языки становятся все более популярными, и / или больше ОО-языков начинают реализовывать больше функциональных возможностей (таких как C #), мы, скорее всего, начнем видеть, что Reflection используется все реже и реже.Но я подозреваю, что он всегда будет существовать для более традиционных приложений, таких как плагины (как один из других респондентов отметил).

8 голосов
/ 04 февраля 2010

На самом деле, вы уже используете отражающую систему каждый день : ваш компьютер.

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

Итак, почему установка Linux так хороша, что никто не думает об этом, и страшно для ОО программ?

6 голосов
/ 04 февраля 2010

Без размышлений вам часто приходится много повторяться.

Рассмотрим следующие сценарии:

  • Запустите набор методов, например методы testXXX () в контрольном примере
  • Создание списка свойств в графическом редакторе
  • Сделайте ваши классы скриптовыми
  • Реализация схемы сериализации

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

Фактически, программисты на C / C ++ часто используют язык описания интерфейса для предоставления интерфейсов во время выполнения (предоставляя форму отражения).

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

6 голосов
/ 04 февраля 2010

Я видел хорошее использование с пользовательскими атрибутами. Например, база данных.

[DatabaseColumn("UserID")]
[PrimaryKey]
public Int32 UserID { get; set; }

Затем можно использовать Reflection для получения дополнительной информации об этих полях. Я почти уверен, что LINQ To SQL делает нечто подобное ...

Другие примеры включают тестовые среды ...

[Test]
public void TestSomething()
{
    Assert.AreEqual(5, 10);
}
3 голосов
/ 04 февраля 2010

У Пола Грэма есть большое эссе , которое может сказать это лучше всего:

Программы, которые пишут программы?Когда бы вы хотели это сделать?Не очень часто, если вы думаете, в Cobol.Все время, если подумать на Лиспе.Здесь было бы удобно, если бы я мог привести пример мощного макроса и сказать там!как насчет этого?Но если бы я это сделал, это было бы просто бредом для того, кто не знал Лисп;здесь нет места, чтобы объяснить все, что вам нужно знать, чтобы понять, что это значит.В Ansi Common Lisp я пытался продвигать вещи настолько быстро, насколько мог, и даже в этом случае я не доходил до макросов до страницы 160.

, заканчивая с.,.

За годы нашей работы на Viaweb я прочитал много должностных инструкций.Новый конкурент, казалось, появлялся из дерева каждый месяц или около того.Первое, что я хотел бы сделать, проверив, есть ли у них онлайн-демонстрация, было посмотреть их списки вакансий.Через пару лет я мог сказать, о каких компаниях беспокоиться, а о каких нет.Чем больше в ИТ-специфике описаний работы, тем менее опасной была компания.Самыми безопасными были те, кому нужен опыт Oracle.Вам никогда не приходилось беспокоиться об этом.Вы также были в безопасности, если они сказали, что хотят разработчиков на C ++ или Java.Если бы они хотели программистов на Perl или Python, это было бы немного пугающе - это начинает звучать как компания, где техническая сторона, по крайней мере, управляется настоящими хакерами.Если бы я когда-либо видел объявление о поиске хакеров Lisp, я бы очень волновался.

3 голосов
/ 04 февраля 2010

Программное обеспечение модульного тестирования и платформы, такие как NUnit, используют отражение, чтобы получить список тестов для их выполнения и выполнения. Они находят все наборы тестов в модуле / сборке / двоичном файле (в C # они представлены классами) и все тесты в этих наборах (в C # это методы в классе). NUnit также позволяет пометить тест ожидаемым исключением в случае, если вы тестируете контракты с исключениями.

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

3 голосов
/ 04 февраля 2010

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

Из-за структуры Java вы уже платите за представление своей иерархии классов в памяти во время выполнения (сравните с C ++, где вы не платите никаких затрат, если не используете такие вещи, как виртуальные методы). Поэтому нет никаких оснований для полной блокировки.

Отражение полезно для таких вещей, как сериализация - такие вещи, как Hibernate или digester, могут использовать его для определения того, как лучше всего хранить объекты автоматически. Точно так же модель JavaBeans основана на именах методов (я допускаю сомнительное решение), но вы должны быть в состоянии проверить, какие свойства доступны для создания таких вещей, как визуальные редакторы. В более поздних версиях Java размышления - это то, что делает аннотации полезными: вы можете писать инструменты и выполнять метапрограммирование, используя эти объекты, которые существуют в исходном коде, но могут быть доступны во время выполнения.

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

3 голосов
/ 04 февраля 2010

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

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

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

2 голосов
/ 04 февраля 2010

Я приведу пример решения ac #, которое мне дали, когда я начал изучать.

В нем содержались классы, помеченные атрибутом [Exercise], каждый класс содержал методы, которые не были реализованы (с исключением NotImplementedException).В решении также были модульные тесты, которые не дали результатов.

Цель состояла в том, чтобы реализовать все методы и пройти все модульные тесты.

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

Позже нас попросили реализовать наши собственные методы, а потом еще понять, как пользовательский интерфейс «волшебным образом» был изменен, чтобы включить все новые методы, которые мыреализовано.

Чрезвычайно полезно, но часто плохо понимается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...