Как программно получить аргументы для метода - PullRequest
0 голосов
/ 23 февраля 2020

Как домашнее задание, меня попросили создать группу классов в иерархии, которые принимают различные параметры в своих конструкторах и позволяют пользователю интерактивно создавать объекты таких классов. Для простоты предположим, что классы Base и Child, с Child продолжением Base. Любой будущий класс в проекте будет расширяться Base или одним из его подклассов.

Хотя в центре внимания проекта лежит не написание элегантного или поддерживаемого кода, я пытаюсь написать такой код.

В идеале, после выяснения того, какой объект пользователь хочет создать, у меня был бы способ выяснить, какие параметры необходимы для создания такого объекта, и передать этот список методу, отвечающему за их получение от пользователя. Я думал о методе stati c, getNecessaryArguments(), таком как:

public class Base {
    public static List<String> getNecessaryArguments() {
         return Arrays.asList("name", "age");
    }

    public Base(String name, String age) {
        ...
    }
}

public class Child extends Base {
    public static List<String> getNecessaryArguments() {
         final List<String> baseArgs = Base.getNecessaryArguments();
         baseArgs.addAll(Arrays.asList("position"));
    }

    public Child(final String name, final String age, final String position) {
        super(name, age);
        ...
    }

Я бы тогда сделал что-то вроде UserInterface.requestParameters(XXX.getNecessaryArguments()).

Есть несколько очевидных проблем с этим кодом хотя:

  1. Для каждого класса необходимо поддерживать два списка аргументов параллельно: фактические аргументы конструктора и аргументы, возвращаемые getNecessaryArguments().
  2. Класс, который Child extends должно быть явно упомянуто в его getNecessaryArguments(), поскольку super() не может использоваться в stati c контекстах.

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

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

Ответы [ 3 ]

2 голосов
/ 28 февраля 2020

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

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

public class Base {
    static final Base TEMPLATE = new Base();

    final String name, age;
    protected Base() {
        name = "no name";
        age = null;
    }
    public Base(String name, String age) {
        this.name = name;
        this.age = age;
    }
    public Base(Map<String,String> map) {
        name = map.get("name");
        age = map.get("age");
    }
    public Map<String,String> arguments() {
        HashMap<String,String> map = new HashMap<>();
        map.put("name", name);
        map.put("age", age);
        return map;
    }
}
public class Child extends Base {
    static final Child TEMPLATE = new Child();

    final String position;
    protected Child() {
        position = "unspecified";
    }
    public Child(final String name, final String age, final String position) {
        super(name, age);
        this.position = position;
    }
    public Child(Map<String, String> map) {
        super(map);
        this.position = map.get("position");
    }

    @Override
    public Map<String, String> arguments() {
        final Map<String, String> map = super.arguments();
        map.put("position", position);
        return map;
    }
}

Вы можете использовать его как

Map<String, String> values = Child.TEMPLATE.arguments();
UI.editValues(values);
Child ch = new Child(values);

Это приводит к шаблону Builder, поскольку есть причины заменить generi c Map выделенными классами. Эти классы могут определять свойства с различными типами, а также применять ограничения. Бонусные баллы, если класс может сообщать об ограничениях через интерфейс.

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

public class Base {
    public static class Builder {
        String name = "no name";
        int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = Objects.requireNonNull(name);
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            if(age < 0) throw new IllegalArgumentException();
            this.age = age;
        }
    }

    final String name;
    final int age;
    public Base(String name, int age) {
        if(age < 0) throw new IllegalArgumentException();
        this.name = Objects.requireNonNull(name);
        this.age = age;
    }
    public Base(Builder b) {
        name = b.name;
        age = b.age;
    }
    public void fill(Builder b) {
        b.setName(name);
        b.setAge(age);
    }
}
public class Child extends Base {
    public static class Builder extends Base.Builder {
        String position = "unspecified";
        public String getPosition() {
            return position;
        }
        public void setPosition(String position) {
            this.position = Objects.requireNonNull(position);
        }
    }

    final String position;
    public Child(final String name, final int age, final String position) {
        super(name, age);
        this.position = Objects.requireNonNull(position);
    }
    public Child(Builder b) {
        super(b);
        this.position = b.position;
    }
    public void fill(Builder b) {
        super.fill(b);
        b.setPosition(position);
    }
}

Это можно использовать как

Child.Builder b = new Child.Builder();
UI.editValues(b);
Child c = new Child(b);

Обратите внимание, что эти сборщики следуют шаблону Бина относительно свойства , что очень полезно для универсальных c пользовательских интерфейсов. Конечно, это подразумевает, что пользовательский интерфейс обрабатывает изменяемые объекты, но это также относится к вариантам List и Map. Ключевым моментом является то, что состояние компоновщика будет использоваться для построения неизменяемых объектов в нужное время, например, перед передачей их в последующий код обработки.

0 голосов
/ 24 февраля 2020

В комментариях, которые вы написали:

Я бы предпочел избежать изменчивости

В этом случае вы можете создать Builder для каждого неизменяемого класса , Например, String является неизменным, а StringBuilder, который создает строку, является изменяемым.

Кроме того, информация о переменной может быть сохранена в Builder , поскольку Строитель должен знать все поля, которые нужны классу для его построения.

class Child extends Base {

  private final String position;

  public Child(final String name, final String age, final String position) {
    super(name, age);
    this.position = position;
  }

  /* ... */
}

class ChildBuilder {

  private String name;
  private String age;
  private String position;

  public List<String> parameters() {
    return Arrays.asList("name", "age", "position");
  }

  public ChildBuilder withName(String name) {
    this.name = name;
    return this;
  }

  public ChildBuilder withAge(String age) {
    this.age = age;
    return this;
  }

  public ChildBuilder withPosition(String position) {
    this.position = position;
    return this;
  }

  public Child build() {
    return new Child(name, age, position);
  }
}
0 голосов
/ 23 февраля 2020

Java Фасоль с ConstructorProperties была бы моим выбором, возможно, было бы интересно увидеть некоторые идеи. Например, чтобы иметь параллель с классом bean-компонента Foo, имейте описывающий класс FooBean.

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

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

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

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

В качестве альтернативы вы можете создать класс для этого конструктора, описывающего информацию BeanClassInfo, и иметь в одном классе BeanRegistry:

public class BeanRegistry {

    private Map<Class<? extends Base>, BeanClassInfo> classInfos = new HashMap<>();

    public void registerClass(Class<? extends Base> type, BeanClassInfo classInfo) {
        classInfos.put(type, BeanClassInfo);
    }
    ...
}

public class BeanClassInfo {
    List<String> argNames = ...
}

public class Base {
    protected static BeanRegistry registry = new BeanRegistry();
}

public class Child extends Base {
    static {
        BeanClassInfo classInfo = new BeanClassInfo();
        ...
        registry.registerClass(Child.class, classInfo);
    }

static { ... } является инициализатором c.

Как вы видите, у вас есть один Base.beanRegistry объект, который легко найти. Дочерние классы должны что-то сделать, чтобы «зарегистрироваться».

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