Java Framework / шаблон для изменения значения - PullRequest
0 голосов
/ 05 ноября 2018

У меня есть объект Person

public class Person {
    private String name;
    private Integer age ;
    private String address;
    private String group;
    private String id;

    public Person(String name, Integer age, String address, String group, String id) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.group = group;
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", group='" + group + '\'' +
                ", id='" + id + '\'' +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }

    public String getGroup() {
        return group;
    }

    public String getId() {
        return id;
    }
}

метаданные

public class MetaData {
    private String fieldName;
    private String comparator;
    private String fieldValue;
    private String newFieldName;
    private String newFieldValue;

    public MetaData(String fieldName, String comparator, String fieldValue, String newFieldName, String newFieldValue) {
        this.fieldName = fieldName;
        this.comparator = comparator;
        this.fieldValue = fieldValue;
        this.newFieldName = newFieldName;
        this.newFieldValue = newFieldValue;
    }

    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public String getComparator() {
        return comparator;
    }

    public void setComparator(String comparator) {
        this.comparator = comparator;
    }

    public String getFieldValue() {
        return fieldValue;
    }

    public void setFieldValue(String fieldValue) {
        this.fieldValue = fieldValue;
    }

    public String getNewFieldName() {
        return newFieldName;
    }

    public void setNewFieldName(String newFieldName) {
        this.newFieldName = newFieldName;
    }

    public String getNewFieldValue() {
        return newFieldValue;
    }

    public void setNewFieldValue(String newFieldValue) {
        this.newFieldValue = newFieldValue;
    }
}

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

например

public class TestPersonModifier {

    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("John", 22, "London", "BETA", "ABC"));
        personList.add(new Person("Mathew", 44, "Glasgow", "ALPHA", "XYZ"));

        List<MetaData> metaData = new ArrayList<>();

        metaData.add(new MetaData("name", "EQUALS", "John", "group", "SIGMA"));
        metaData.add(new MetaData("name", "EQUALS", "Mathew", "address", "Singapore"));

        //g
        personList.forEach(p -> {

            metaData.forEach(m -> {

                if (m.getFieldName().equals("name")) {
                    if (m.getComparator().equals("EQUALS")) {

                        if (p.getName().equals(m.getFieldValue())) {

                            if (m.getNewFieldName().equals("group")) {

                                System.out.println(p.getId() + "::The group value changed from " + p.getGroup() + " to " + m.getNewFieldValue());
                                p.setGroup(m.getNewFieldValue());
                            }
                            if (m.getNewFieldName().equals("address")) {
                                System.out.println(p.getId() + "::The address value changed from " + p.getAddress() + " to " + m.getNewFieldValue());

                                p.setGroup(m.getNewFieldValue());
                            }
                        }
                    }
                }
            });
        });

    }
}

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

Существует ли языковая структура выражений, которую я могу использовать, или библиотека JAva, или любая открытая среда soure, чтобы сделать ее универсальным решением

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Есть в основном три ловушки:

  • Во-первых, для доступа к значению поля из класса модели POJO необходимо какое-то отображение из имени поля в метод получения / установки.
  • Во-вторых, тип данных отдельного поля отличается. Поэтому вам нужен другой компаратор, если значение поля не реализует Comparable. Используемые вами типы реализуют Comparable, так что у вас все хорошо.
  • В-третьих, Java не может преобразовывать встроенные типы значений из или в Object универсальным способом; то есть сигнатуры методов-получателей вообще не совпадают. Вы уже избежали этого, выбрав упакованные типы для тривиальных типов данных, таких как int.

Чтобы все это заработало, вам нужно выполнить следующие шаги:

  1. Итак, на первом шаге вам необходимо создать репозиторий ваших методов получения . То есть что-то вроде HashMap<String, Function<Person, Object>> map1. Вы можете сделать это статически, добавив все получатели явно с помощью map1.put("name", Person::getName) или с помощью отражения.

  2. Далее вам нужно построить репозиторий для сеттеров . То есть что-то вроде HashMap<String, BiConsumer<Person, Object>> map2. Снова вы можете использовать отражение или явную инициализацию. В последнем случае есть другая ловушка, потому что преобразование универсального типа для установщика больше не является ковариантным. Это может быть обработано двойным непроверенным броском:

    map2.put("name", (BiConsumer)(BiConsumer<Person, String>)Person::setName);
    

    Из-за стирания типа Javas все сеттеры имеют одинаковую сигнатуру метода.

  3. Предполагая, что все ваши типы значений реализуют Comparable, следующий шаг довольно прост.
    Вызовите ваш геттер из репозитория, сравните значения (будьте внимательны с нулями) и примените нужный оператор сравнения (= 0, < 0, > 0 или любой другой) - кстати: вам действительно следует использовать enum для MetaData::comparator вместо строки.

  4. Присвойте полученное значение, если необходимо, вызовом установщика из хранилища.

Если вам не требуется указывать строку в качестве ключа в качестве имени поля, вы можете присоединиться к шагу 1 и шагу 2. Просто используйте ссылку на метод получения как (строго типизированный) ключ вместо имени. Теперь вам нужен только один репозиторий, который отображается из геттера в сеттер. I.e.:

HashMap<Function<Person, Object>, BiConsumer<Person, Object>> map;

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

0 голосов
/ 05 ноября 2018

Если вы довольны несколько менее удобной формой

import com.google.common.base.Equivalence;
...
metaData.add(new MetaData(Person.class, Equivalence.equals(), "name", "John", "group", "SIGMA"));

самое простое решение, основанное только на Java, заключается в добавлении класса интроспективных экземпляров в метаданные, параметризации способа сравнения объектов (здесь я использовал Guava, вы также можете использовать простую Java 8 BiPredicate<T super Person>) и указывать имена свойств.

При выполнении вы можете использовать java.beans.Introspector#getBeanInfo() для обнаружения имени получателя и установщика (это самый безопасный способ преодоления различных неприятностей в Java Beans, таких как логические свойства, полностью прописные имена свойств и т. Д. - я рекомендую предпочесть его наполовину запеченные инструменты на основе замены строк) и вызывайте их соответствующим образом.

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

metaData.add(new MetaData<Person>(Equivalence.equals(), "name", "John", "group", "SIGMA"));

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

или создание собственного подкласса, такого как EqualsMetadata или PersonMetadata, вы получите идею.


В качестве альтернативы, вы можете вообще избежать (простого старого) отражения и определить все с помощью ссылок на методы:

metaData.add(new MetaData(Equivalence.equals(), Person::getName, Person::setName, "John", Person::getGroup, Person::setGroup, "SIGMA"));

, который длиннее, но безопаснее - вы можете убедиться, что аргументы John и SIGMA соответствуют типу getter и setter.


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

...