Как Отражение не могло вести для кодирования запахов?

Я происхожу из низкоуровневых языков - C++ является высшим уровнем, в котором я программирую.

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

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

Я неправильно? Я неправильно понимаю понятие и утилиту Отражения?

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

Просветите этого увальня низкого уровня.

63
задан Norman Ramsey 4 February 2010 в 01:25
поделиться

17 ответов

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

Давайте напишем 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 - насмешливый фреймворк.Вы передаете тип интерфейса в метод, и за кулисами инфраструктура динамически создает и создает экземпляр фиктивного объекта, реализующего интерфейс.

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

Метаданные!

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

[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) { ... }

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

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

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

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

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

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

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

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

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

Позднее связывание / Duck Typing

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

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

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

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

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

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

90
ответ дан 24 November 2019 в 16:13
поделиться

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

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

    ///--------------------------------------------------------------------------------
    /// <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;
        }
    }
1
ответ дан 24 November 2019 в 16:13
поделиться

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

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

-121--926070-

Являются ли эти два других острых пика и впадины также комплексами qrs?

Я думаю, что вам нужно вычислить наклон этого графика в каждой точке. Затем также необходимо увидеть, как быстро изменяется уклон (2-я производная???). Если у вас резкое изменение, то вы знаете, что достигли какого-то резкого пика. Конечно, вы хотите ограничить обнаружение изменения, так что вы можете сделать что-то вроде «если наклон изменяется на X через временной интервал T», так что вы не возьмете крошечные удары на графике.

Давно я не занимался математикой... и это кажется математическим вопросом;) О, и я тоже не делал никакого анализа сигналов:).

Просто добавьте еще одну точку. Вы также можете попробовать усреднить сигнал, я думаю. Например, усреднение последних 3 или 4 точек данных. Я думаю, что вы можете обнаружить резкие изменения, которые тоже путь.

-121--855294-

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

1
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

class SomeOtherClass(object):
    def __getattr__(self, attr_name):
        # do something nice and then call method that caller requested
        getattr(self.someclass_instance, attr_name)()
1
ответ дан 24 November 2019 в 16:13
поделиться

Вставьте некоторое вертикальное пространство

blah blah blah \\
\vspace{1cm}

для масштабирования шрифта, используйте ex (высота нижнего регистра «x») в качестве единицы измерения, и есть различные предопределенные длины , связанные с доступным интервалом между строками, вы можете быть особенно заинтересованы в baselineskip .

-121--1003004-

Помните, что исключение содержит сведения о том, где создается исключение. Поскольку конструктор понятия не имеет, где он должен быть брошен, тогда имеет смысл только то, что метод броска вводит эти детали в объект в точке броска. Другими словами, CLR пытается ввести данные в значение NULL, что запускает "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "

Не уверен, что это именно то, что происходит, но это объясняет явление.

Если предположить, что это правда (и я не могу думать, что лучший способ получить ex, чтобы быть null, чем бросить null;), это будет означать, что ex не может быть null.

-121--849255-

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

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

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

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

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

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

2
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

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

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

Все дело в быстром развитии.

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);
}
-121--926069-

Плагины являются отличным примером.

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

2
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

3
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

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

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

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

6
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

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

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

11
ответ дан 24 November 2019 в 16:13
поделиться

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

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

{{1 }}
1
ответ дан 24 November 2019 в 16:13
поделиться

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

РЕДАКТИРОВАТЬ: орфография

2
ответ дан 24 November 2019 в 16:13
поделиться

Все дело в быстром развитии.

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);
}
2
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

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

3
ответ дан 24 November 2019 в 16:13
поделиться

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

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

3
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

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

[Test]
public void TestSomething()
{
    Assert.AreEqual(5, 10);
}
6
ответ дан 24 November 2019 в 16:13
поделиться

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

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

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

.
8
ответ дан 24 November 2019 в 16:13
поделиться

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

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

в заключение: . . .

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

3
ответ дан 24 November 2019 в 16:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: