user3166722
user3166722

Reputation: 89

How to use HashMap as builder?

i am trying to run some methods depending on the string parameters arriving me. The idea is to activate and deactive some functions from a list of "Terminals". The class is designed for a Command pattern, "doProcess" will be called in some time

My actual solution is awful, so I think about making a HashMap. For example: map.put("Mail",activateMailsOfTerminals)

But I dont know how to call the functions (activateMailsOfTerminals,deactivate...) dynamically depending on the list of string that comes on the constructor

Thank you very much!

Bad solution:

public class ActionsActivator {

    Set < Terminal > terminals = new HashSet < > ();
    Set < String > activate = new HashSet < > ();
    Set < String > deactivate = new HashSet < > ();

    public ActionsActivator(Set < Terminal > filteredTerminals, Set < String > active, Set < String > deactive) {
        this.terminals = Map().getInstance().getTerminals;
        this.activate.addAll(activate);
        this.deactivate.addAll(deactivate);
    }

    public void doProcess() {
        if (hasElement(activate, "Mail")) terminals.forEach(terminal - > terminal.activateMails());
        if (hasElement(activate, "Register")) terminals.forEach(terminal - > terminal.activateRegisters());

        if (hasElement(deactivate, "Mail")) terminals.forEach(terminal - > terminal.deactivateMails());
        if (hasElement(deactivate, "Register")) terminals.forEach(terminal - > terminal.deactivateRegisters());
    }

    private Boolean hasElement(Set < String > list, String element) {
        return list.stream().anyMatch(elem - > elem.equals(element));
    }
}

Upvotes: 4

Views: 1503

Answers (2)

Bohemian
Bohemian

Reputation: 425208

Use a map:

private static Map<String, Consumer<Terminal>> activations = new HashMap<String, Consumer<Terminal>>() {{
    put("Mail", Terminal::activateMails);
    put("Register", Terminal::activateRegisters);
    // etc
}};
// similar for deactivate

Then to use:

public void doProcess() {
    activate.stream()
        .map(activations::get)
        .filter(a -> a != null)
        .forEach(a -> terminals.forEach(a));
    // similar for deactivate
}

This approach handles an arbitrarily large number of actions declaratively.

Upvotes: 0

augray
augray

Reputation: 3161

If I am correct in my understanding, you have a set of "terminals", and you want an object which when "activated" performs some method invocation/invocations on the terminals provided. The following should accomplish and demo that:

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

public class CommandsDemo {

    public static class ActionsActivator{
        private Set<Terminal> terminals;
        private Set<Consumer<Terminal>> actionsToPerformOnTerminals = new HashSet<>();

        public ActionsActivator(Set<Terminal> terminals, Consumer<Terminal>... actionsForTerminals){
            this.terminals = terminals;
            actionsToPerformOnTerminals.addAll(Arrays.asList(actionsForTerminals));
        }

        public void doProcess(){
            for(Consumer<Terminal> action : actionsToPerformOnTerminals){
                terminals.stream().forEach(action);
            }
        }
    }

    public static class Terminal{
        private final String name;

        public Terminal(String name){
            this.name = name;
        }

        public void activateMails(){
            System.out.println("Activated mail for: "+name);
        }
        public void activateRegisters(){
            System.out.println("Activated registers for: "+name);
        }
    }

    public static void main(String[] args){
        Terminal terminalA = new Terminal("terminalA");
        Terminal terminalB = new Terminal("terminalB");
        Terminal terminalC = new Terminal("terminalC");

        Set<Terminal> terminals = new HashSet<>();
        terminals.addAll(Arrays.asList(terminalA, terminalB, terminalC));

        ActionsActivator mailActivator = new ActionsActivator(terminals, (terminal)->terminal.activateMails());

        ActionsActivator registerActivator = new ActionsActivator(terminals, (terminal)->terminal.activateRegisters());

        ActionsActivator mailAndRegisterActivator = new ActionsActivator(terminals, 
                (terminal)->terminal.activateMails(),
                (terminal)->terminal.activateRegisters());

        System.out.println("Running mail activator");
        mailActivator.doProcess();

        System.out.println("Running register activator");
        registerActivator.doProcess();

        System.out.println("Running mail & register activator");
        mailAndRegisterActivator.doProcess();
    }

}

Output:

Running mail activator
Activated mail for: terminalC
Activated mail for: terminalA
Activated mail for: terminalB
Running register activator
Activated registers for: terminalC
Activated registers for: terminalA
Activated registers for: terminalB
Running mail & register activator
Activated registers for: terminalC
Activated registers for: terminalA
Activated registers for: terminalB
Activated mail for: terminalC
Activated mail for: terminalA
Activated mail for: terminalB

The key thing that makes it possible for us to configure the command/commands to be invoked on each terminal is the generic Consumer class, which represents the ability to be invoked with an instance of an object of the generically declared type as an argument. Ex:

Consumer<String> myConsumer = (str)->{System.out.println("Hello, "+str)};

myConsumer.accept("World");
myConsumer.accept("Venus");
myConsumer.accept("Mars");

prints:

Hello, World
Hello, Venus
Hello, Mars

So by passing in a series of Consumer<Terminal> objects which consume a Terminal and invoke a desired method on said Terminals, we can make an ActionsActivator which invokes a configurable method/methods on the provided terminals.

Upvotes: 2

Related Questions