Правильный способ объявить и установить частную конечную переменную-член из конструктора в Java? - PullRequest
2 голосов
/ 17 ноября 2009

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

public class Base {
    private final Map<String, Command> availableCommands;
    public Base() {
        availableCommands = Helper.loadCommands();  
    }
}

В приведенном выше примере вспомогательный класс выглядит следующим образом:

public class Helper {
    public static Map<String, Command> loadCommands() {
        Map<String, Command> commands = new HashMap<String, Command>();
        commands.put("A", new CommandA());
        commands.put("B", new CommandB());
        commands.put("C", new CommandC());

        return commands;
    }
}

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

public class Base {
    private final Map<String, Command> availableCommands;
    public Base() {
        this.setCommands();  
    }
    private void setCommands() {
        this.availableCommands = Helper.loadCommands();
    }
}

Но теперь я не могу поддерживать модификатор final и получаю ошибку компилятора (переменная Final не может быть установлена)

Другой способ сделать это будет:

public class Base {
    private final Map<String, Command> availableCommands = new HashMap<String, Command>();
    public Base() {
        this.setCommands();
    }
    private void setCommands() {
        Helper.loadCommands(availableCommands);
    }
}

Но в этом случае метод в классе Helper изменится на:

public static void loadCommands(Map<String, Command> commands) {
    commands.put("A", new CommandA());
    commands.put("B", new CommandB());
    commands.put("C", new CommandC());
}

Таким образом, разница в том, где я могу создать новую карту с new HashMap<String, Command>();? Мой главный вопрос, если есть рекомендуемый способ сделать это, учитывая, что часть функциональности происходит от статического этого помощника метод, как способ загрузить актуальную карту с записями?

Создаю ли я новую карту в своем Базовом классе или в классе Helper? В обоих случаях Helper будет выполнять фактическую загрузку, а ссылка Base на карту с конкретными командами будет закрытой и окончательной.

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

Ответы [ 7 ]

3 голосов
/ 17 ноября 2009

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

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

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

2 голосов
/ 17 ноября 2009

Рассматривали ли вы использование шаблона Builder, подобного шаблону в Effective Java 2nd ed. ?

Вы можете захватить всю логику построения карты в одном месте (таким образом, у вас не будет 2 отдельных классов для обслуживания). База будет выглядеть так:

public class Base {

    private final Map<String, Command> commands;

    private Base(Builder b) {
        commands = b.commands;
    }

    public static class Builder() {

        private final Map<String, Command> commands;

        public Builder() {
            commands = new HashMap<String, Command>();
        }

        public Builder addCommand(String name, Command c) {
            commands.put(name, c);
            return this;
        }

        public Base build() {
            return new Base(this);
        }
    }
}

Клиенты базы теперь будут работать так:

Base b = new Base.Builder().addCommand("c1", c1).addCommand("c2", c2).build();

Апшот в том, что клиентскому классу не нужно знать, что ему нужно построить Карту, и вы можете по существу построить все это одной строкой. Недостатком является то, что Base не может быть расширен, потому что конструктор теперь закрыт (возможно, вы этого хотите, а может и нет).

РЕДАКТИРОВАТЬ: У меня была ошибка в build (), где я вместо этого передавал команды, как я и предполагал изначально РЕДАКТИРОВАТЬ 2: ошибочно вызывается добавить вместо вместо того, чтобы положить в Base.Builder.addCommand

2 голосов
/ 17 ноября 2009

Если вы хотите, чтобы такие карты были неизменяемыми, взгляните на Google Collection API . Цитировать связанную документацию:

static final ImmutableMap<String, Integer> WORD_TO_INT =
       new ImmutableMap.Builder<String, Integer>()
           .put("one", 1)
           .put("two", 2)
           .put("three", 3)
           .build();
1 голос
/ 17 ноября 2009

Почему бы тебе просто не сделать

private final Map<String, Command> availableCommands = Helper.loadCommands();  

1 голос
/ 17 ноября 2009
  1. Если вы хотите, чтобы он был постоянным, вам не нужно использовать сторонние API, вы можете использовать: java.util.Collections.unmodifiableMap(Map m)

  2. Наиболее распространенный способ сделать это будет:

public class Base {
private final Map availableCommands;
public Base(){
  availableCommands=new HashMap(); // or any other kind of map that you wish to load
  availableCommands = Helper.loadCommands(availableCommands);  
 }
}
0 голосов
/ 17 ноября 2009

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

public class Base {
    public final Map< String, Command > availableCommands;

    public Base() {
        availableCommands = Collections.unmodifiableMap( new HashMap() {
            {
                put( "A", new CommandA() );
                put( "B", new CommandB() );
            }
        } );
    }
}
0 голосов
/ 17 ноября 2009

Я бы лично переименовал класс Helper в что-то вроде CommandHolder:

public class CommandHolder {
    private static Map<String, Command> availableCommands;
    private static CommandHolder instance;

    private CommandHolder{}
    public static synchronized Map<String, Command> getCommandMap() {
        if (instance == null) {
            instance = new CommandHolder();
            instance.load();
        }
        return availableCommands
    }
    private void load() {
        ...
    }
}

синхронизировано, чтобы убедиться, что загрузка происходит только один раз. Нет getCommand, потому что что тогда тоже нужно синхронизировать, и каждый поиск будет дороже. Я предполагаю, что карта доступна только для чтения, иначе вам все равно понадобится synchronizedMap в многопоточной среде.

...