Чи погана звичка (над) використовувати рефлексію?


16

Чи є хорошою практикою використання рефлексії, якщо сильно зменшується кількість кодового коду?

В основному існує компроміс між продуктивністю та, можливо, читабельністю з одного боку та абстракцією / автоматизацією / зменшенням кодового коду з іншого боку.

Редагувати: Ось приклад рекомендованого використання рефлексії .

Для прикладу, припустимо, є абстрактний клас, Baseякий має 10 полів і має 3 підкласи SubclassA, SubclassBі SubclassCкожне з 10 різними полями; всі вони прості боби. Проблема полягає в тому, що ви отримуєте два Baseпосилання типу і хочете дізнатися, чи відповідні їм об'єкти одного (під) типу і рівні.

В якості рішень є неочищене рішення, в якому спочатку ви перевіряєте, чи типи рівні, а потім перевіряєте всі поля, або ви можете використовувати відображення і динамічно бачити, чи є вони одного типу, і повторювати всі методи, які починаються з "отримати" (конвенція над конфігурацією), викликайте їх на обох об'єктах і виклик дорівнює за результатами.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}

20
Зловживання чим-небудь - шкідлива звичка.
Тулен Кордова

1
@ user61852 Правильно, занадто велика свобода призводить до диктатури. Деякі старі греки вже знали про це.
ott--

6
"Занадто багато води було б погано для вас. Очевидно, що занадто багато саме тієї кількості, яка надмірна - ось що це означає! "- Стівен Фрай
Джон Перді

4
"У будь-який час ви описуєте код форми", якщо об'єкт типу T1, то робіть щось, але якщо це тип T2, то робіть щось інше ", ляпніть
rm5248

Відповіді:


25

Відображення була створена для певної мети, щоб відкрити функціональність класу , який був відомий під час компіляції, схоже на те , що dlopenі dlsymфункції роблять в C. Будь-яке використання поза , які повинні бути в значній мірі вивчені.

Чи траплялось вам коли-небудь, що самі дизайнери Java стикалися з цією проблемою? Тому практично в кожному класі є equalsметод. У різних класах є різні визначення рівності. У деяких умовах похідний об'єкт може бути рівним базовому об'єкту. За деяких обставин рівність можна було б визначити на основі приватних полів без дільниць. Ви не знаєте.

Ось чому кожен об'єкт, який хоче користувацьку рівність, повинен реалізувати equalsметод. Врешті-решт, ви захочете поставити об’єкти у набір або використовувати їх як хеш-індекс, тоді вам доведеться equalsвсе-таки реалізувати . Інші мови роблять це інакше, але Java використовує equals. Ви повинні дотримуватися умов вашої мови.

Також код «котлопластика», якщо його поставити в потрібний клас, досить важко викрутити. Рефлексія додає додаткової складності, означає додаткові шанси на помилки. Наприклад, у вашому методі два об'єкти вважаються рівними, якщо один повертається nullдля певного поля, а другий - ні. Що робити, якщо хтось із ваших жителів поверне один із ваших об'єктів без відповідного equals? Ваша if (!object1.equals(object2))невдача. Крім того, що це робить схильним помилок - це той факт, що рефлексія використовується рідко, тому програмісти не настільки знайомі з її дітьми.


12

Перевикористання рефлексії, ймовірно, залежить від мови, що використовується. Тут ви використовуєте Java. У цьому випадку рефлексію слід використовувати обережно, оскільки часто це лише вирішення поганого дизайну.

Отже, ви порівнюєте різні класи, це ідеальна проблема переосмислення методу . Зауважте, що екземпляри двох різних класів ніколи не повинні вважатися рівними. Ви можете порівнювати рівність, лише якщо у вас є екземпляри одного класу. Див. Https://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-java для прикладу, як правильно виконати порівняння рівності.


16
+1 - Деякі з нас вважають, що будь-яке використання відображення - це червоний прапор, що вказує на поганий дизайн.
Росс Паттерсон

1
Найімовірніше, що в цьому випадку бажане рішення, засноване на рівних, але як загальне уявлення, що не так з рішенням відображення? Насправді це дуже загально, і методи рівних не потрібно чітко писати в кожному класі та підкласі (хоча вони легко можуть бути згенеровані хорошим IDE).
m3th0dman

2
@RossPatterson Чому?
m3th0dman

2
@ m3th0dman Тому що це призводить до таких речей, як ваш compare()метод, припускаючи, що будь-який метод, що починається з "get", - це отримувач і його безпечно та доцільно викликати як частину операції порівняння. Це порушення призначеного інтерфейсу визначення об'єкта, і, хоча це може бути доцільним, це майже завжди неправильно.
Росс Паттерсон

4
@ m3th0dman Це порушує інкапсуляцію - базовий клас має доступ до атрибутів у своїх підкласах. Що робити, якщо вони приватні (або getter private)? А як щодо поліморфізму? Якщо я вирішу додати інший підклас і хочу порівняти в ньому порівняння? Ну, базовий клас це вже робить для мене, і я не можу його змінити. Що робити, якщо геттери щось навантажують? Чи хочу я зробити метод порівняння? Як я навіть знаю, що метод починається з того get, що це геттер, а не власний метод, який повертає щось?
Султан

1

Я думаю, у вас тут два питання.

  1. Скільки динамічного та статичного коду я повинен мати?
  2. Як я можу висловити власну версію рівності?

Динамічний проти статичного коду

Це питання багаторічне, і відповідь дуже сумнівна.

З одного боку, ваш компілятор дуже добре вловлює всілякі погані коди. Це робиться за допомогою різних форм аналізу. Типовий аналіз є загальним. Він знає, що ви не можете використовувати Bananaоб'єкт у коді, який очікує Cog. Це повідомляє вам про помилку компіляції.

Тепер він може робити це лише тоді, коли він може виводити як прийнятий Тип, так і даний тип з контексту. Скільки можна зробити висновок і наскільки загальним є такий висновок, багато в чому залежить від мови, якою використовується. Java може виводити інформацію про тип за допомогою таких механізмів, як "Спадщина", "Інтерфейси" та "Загальна версія". Пробіг дійсно відрізняється, деякі інші мови надають менше механізмів, а деякі - більше. Він все ще зводиться до того, що компілятор може знати, що це правда.

З іншого боку, ваш компілятор не може передбачити форму закордонного коду, і іноді загальний алгоритм може бути виражений для багатьох типів, які неможливо легко виразити за допомогою системи типів мови. У цих випадках компілятор не завжди може знати результат заздалегідь, і він навіть не може знати, яке питання потрібно задати. Відображення, інтерфейси та клас Object - це спосіб вирішення цих проблем Java. Вам доведеться надати правильну перевірку та обробку, але не такий здоровий, щоб мати такий код.

Чи зробити ваш код дуже конкретним, або дуже загальним, зводиться до проблеми, з якою ви намагаєтеся впоратися. Якщо ви можете легко висловити це за допомогою системи типу, зробіть це. Нехай компілятор відіграє свої сили та допоможе вам. Якщо система типу не могла знати заздалегідь (закордонний код), або система типів погано підходить для загальної реалізації вашого алгоритму, то відображення (та інші динамічні засоби) є правильним інструментом для використання.

Просто майте на увазі, що вихід за межі системи типу вашої мови дивує. Уявіть, що ви йдете до свого друга і починаєте розмову англійською мовою. Раптом киньте кілька слів з іспанської, французької та кантонської мов, які точно висловлюють ваші думки. Контекст багато розповість вашому другу, але вони також можуть не знати, як поводитися з тими словами, що призводять до різного роду непорозумінь. Чи краще розбиратися з тими непорозуміннями, ніж пояснювати ці ідеї англійською мовою, використовуючи більше слів?

Спеціальна рівність

Хоча я розумію, що Java в основному покладається на equalsметод загального порівняння двох об'єктів, не завжди підходящих у даному контексті.

Є інший спосіб, і він є стандартом Java. Його називають компаратором .

Від того, як ви реалізуєте ваш компаратор, буде залежати від того, що ви порівнюєте, і як.

  • Він може бути застосований до будь-яких двох об'єктів незалежно від їх конкретної equalsреалізації.
  • Він міг би реалізувати загальний (на основі роздумів) метод порівняння для обробки будь-яких двох об'єктів.
  • Функції порівняння котлових плит можуть бути додані для типів об'єктів, що часто порівнюються, забезпечуючи безпеку і оптимізацію типу.

1

Я вважаю за краще максимально уникати рефлексивного програмування

  • робить код важче статично перевірити компілятором
  • робить код важче міркувати
  • робить код складнішим для рефактора

Це також набагато менш ефективно, ніж виклики простих методів; це було повільніше на порядок і більше.

Статично перевірка коду

Будь-який відображаючий код шукає класи та методи за допомогою рядків; в оригінальному прикладі він шукає будь-який метод, починаючи з "отримати"; він поверне getters, але інші методи на додаток, наприклад, "gettysburgAddress ()". Правила можна посилити в коді, але суть залишається в тому, що це перевірка виконання ; IDE і компілятор не можуть допомогти. Взагалі мені не подобається "строго набраний" або "примітивний одержимий" код.

Важче міркувати про

Відображаючий код є більш багатослівним, ніж виклики простих методів. Більше коду = більше помилок або принаймні більший потенціал для помилок, більше коду для читання, тестування тощо. Менше - більше.

Важче для Refactor

Оскільки код заснований строго / динамічно; як тільки відображення з’являється на сцені, ви не можете рефакторний код зі 100% надійністю, використовуючи інструменти рефакторингу IDE, оскільки IDE не може підібрати рефлекторні використання.

В основному уникайте відображення в загальному коді, якщо це можливо; шукайте вдосконалений дизайн.


0

Зловживання чим-небудь за визначенням погано, правда? Тож давайте позбудемося (над) зараз.

Ви назвали б надзвичайно важке внутрішнє використання відображення весни поганою звичкою?

Весна приручає відображення за допомогою анотацій - так це і впадає в сплячку (і, мабуть, десятки / сотні інших інструментів).

Дотримуйтесь цих шаблонів, якщо ви використовуєте його у власному коді. Використовуйте примітки, щоб переконатися, що IDE користувача все ще може їм допомогти (Навіть якщо ви є єдиним "Користувачем" свого коду, необережне використання відображення, ймовірно, повернеться, щоб зрештою вас кусати в задник).

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


0

Я думаю, що більшість із цих відповідей пропускають суть.

  1. Так, ви, ймовірно, повинні писати equals()і hashcode(), як зазначає @KarlBielefeldt.

  2. Але для класу з багатьма полями це може бути виснажливою котельною плитою.

  3. Отже, це залежить .

    • Якщо вам потрібні лише рівні і хеш-коди рідко , використовувати прапори обчислення загальної мети прагматично і, ймовірно, добре. Принаймні, як швидкий і брудний перший прохід.
    • але якщо вам потрібно багато рівних, наприклад, ці об’єкти потрапляють у HashTables, так що продуктивність буде проблемою, ви обов'язково повинні написати код. це буде швидше.
  4. Інша можливість: якщо у вас на заняттях дійсно так багато полів, що написання рівних є стомлюючим, подумайте

    • внесення полів у карту.
    • Ви все ще можете писати власні геттери та сетери
    • використовувати Map.equals()для порівняння. (або Map.hashcode())

наприклад ( зауважте : ігноруючи нульові перевірки, ймовірно, слід використовувати Enums замість String-клавіш, багато чого не показано ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.