Как отражение может привести к запаху кода?

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

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

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

Неужели я ошибаюсь? Не понимаю ли я концепцию и полезность Reflection?

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

Просветите этот низкоуровневый lubber.

Ответ 1

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

Пусть написано 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 frameworks для .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) { ... }

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

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Итак, почему это так нормально для установки Linux, чтобы быть рефлексивным, что никто даже не думает об этом, и страшно для программ OO?

Ответ 4

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

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

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

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

[Test]
public void TestSomething()
{
    Assert.AreEqual(5, 10);
}

Ответ 5

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

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

  • Выполнить набор методов, например. методы testXXX() в тестовом примере
  • Создание списка свойств в построителе gui
  • Сделайте ваши классы доступными для сценариев
  • Внедрить схему сериализации

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

Программы, которые пишут программы? когда вы когда-нибудь захотите это сделать? Не очень часто, если вы думаете в Cobol. Все время, если вы думаете в Lisp. Это было бы удобно здесь, если бы я мог дайте пример мощного макроса, и сказать там! как насчет этого? Но если Я сделал, это будет просто выглядеть тарабарщину тому, кто не знал Lisp; здесь нет места для объяснения все, что вам нужно знать понять, что это значит. В Анси Общий Lisp Я пытался переместить вещи так быстро, как я мог, и даже так Я не добирался до макросов до страницы 160.

завершение с.,.

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

Ответ 10

Это все о быстром развитии.

var myObject = // Something with quite a few properties.
var props = new Dictionary<string, object>();
foreach (var prop in myObject.GetType().GetProperties())
{
    props.Add(prop.Name, prop.GetValue(myObject, null);
}

Ответ 11

Плагины - отличный пример.

Инструменты - еще один пример - инструменты инспектора, инструменты сборки и т.д.

Ответ 12

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

EDIT: правописание

Ответ 13

Я приведу пример решения С#, которое было дано, когда я начал учиться.

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

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

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

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

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

Ответ 14

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

Кроме того, если вы делаете редактор, который действительно не заботится о базовой модели, а скорее о том, как объекты структурированы напрямую, ala System.Forms.PropertyGrid)

Ответ 15

Без отражения архитектура плагина не будет работать!

Ответ 16

Очень простой пример в Python. Предположим, у вас есть класс, который имеет 3 метода:

class SomeClass(object):
    def methodA(self):
       # some code
    def methodB(self):
       # some code
    def methodC(self):
       # some code

Теперь, в каком-то другом классе вы хотите украсить эти методы некоторым дополнительным поведением (т.е. вы хотите, чтобы этот класс имитировал SomeClass, но с дополнительной функциональностью). Это так же просто, как:

class SomeOtherClass(object):
    def __getattr__(self, attr_name):
        # do something nice and then call method that caller requested
        getattr(self.someclass_instance, attr_name)()

Ответ 17

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

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

    ///--------------------------------------------------------------------------------
    /// <summary>Transform data from the input data reader into the output object.  Each
    /// element to be transformed must have the DataElement attribute associated with
    /// it.</summary>
    ///
    /// <param name="inputReader">The database reader with the input data.</param>
    /// <param name="outputObject">The output object to be populated with the input data.</param>
    /// <param name="filterElements">Data elements to filter out of the transformation.</param>
    ///--------------------------------------------------------------------------------
    public static void TransformDataFromDbReader(DbDataReader inputReader, IDataObject outputObject, NameObjectCollection filterElements)
    {
        try
        {
            // add all public properties with the DataElement attribute to the output object
            foreach (PropertyInfo loopInfo in outputObject.GetType().GetProperties())
            {
                foreach (object loopAttribute in loopInfo.GetCustomAttributes(true))
                {
                    if (loopAttribute is DataElementAttribute)
                    {
                        // get name of property to transform
                        string transformName = DataHelper.GetString(((DataElementAttribute)loopAttribute).ElementName).Trim().ToLower();
                        if (transformName == String.Empty)
                        {
                            transformName = loopInfo.Name.Trim().ToLower();
                        }

                        // do transform if not in filter field list
                        if (filterElements == null || DataHelper.GetString(filterElements[transformName]) == String.Empty)
                        {
                            for (int i = 0; i < inputReader.FieldCount; i++)
                            {
                                if (inputReader.GetName(i).Trim().ToLower() == transformName)
                                {
                                    // set value, based on system type
                                    loopInfo.SetValue(outputObject, DataHelper.GetValueFromSystemType(inputReader[i], loopInfo.PropertyType.UnderlyingSystemType.FullName, false), null);
                                }
                            }
                        }
                    }
                }
            }

            // add all fields with the DataElement attribute to the output object
            foreach (FieldInfo loopInfo in outputObject.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance))
            {
                foreach (object loopAttribute in loopInfo.GetCustomAttributes(true))
                {
                    if (loopAttribute is DataElementAttribute)
                    {
                        // get name of field to transform
                        string transformName = DataHelper.GetString(((DataElementAttribute)loopAttribute).ElementName).Trim().ToLower();
                        if (transformName == String.Empty)
                        {
                            transformName = loopInfo.Name.Trim().ToLower();
                        }

                        // do transform if not in filter field list
                        if (filterElements == null || DataHelper.GetString(filterElements[transformName]) == String.Empty)
                        {
                            for (int i = 0; i < inputReader.FieldCount; i++)
                            {
                                if (inputReader.GetName(i).Trim().ToLower() == transformName)
                                {
                                    // set value, based on system type
                                    loopInfo.SetValue(outputObject, DataHelper.GetValueFromSystemType(inputReader[i], loopInfo.FieldType.UnderlyingSystemType.FullName, false));
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            bool reThrow = ExceptionHandler.HandleException(ex);
            if (reThrow) throw;
        }
    }

Ответ 18

Одно использование еще не упомянуто: хотя отражение обычно считается "медленным", можно использовать Reflection для повышения эффективности кода, который использует такие интерфейсы, как IEquatable<T>, когда они существуют, и использует другие средства проверки равенства, когда они не. В отсутствие отражения код, который хотел бы проверить, равны ли два объекта, должен был либо использовать Object.Equals(Object), либо проверить во время выполнения, реализован ли объект IEquatable<T> и, если это так, передать объект этому интерфейсу. В любом случае, если тип вещи, которую сравнивают, является типом значения, потребуется хотя бы одна операция по боксу. Использование Reflection позволяет иметь класс EqualityComparer<T> автоматически построить конкретную для типа реализацию IEqualityComparer<T> для любого конкретного типа T, причем эта реализация использует IEquatable<T>, если она определена, или используя Object.Equals(Object), если она не является. В первый раз, когда используется EqualityComparer<T>.Default для любого конкретного типа T, системе придется пройти больше работы, чем потребовалось бы, чтобы проверить, реализует ли конкретный тип IEquatable<T> один раз. С другой стороны, как только эта работа будет завершена, больше не потребуется проверка типа времени выполнения, так как система создаст специально созданную реализацию EqualityComparer<T> для рассматриваемого типа.