Home > Enterprise >  Picocli: How can I decouple @Options attributes from a command class?
Picocli: How can I decouple @Options attributes from a command class?

Time:10-20

I want my app to adhere to the SOLID principles. Using picocli has been a godsend, but annotated @Option attributes are coupled to their respective classes. In my case I want it to be possible to simply put the options into their own classes.

So what I mean is, instead of having to do this:

@Command(name = "finderapp")
class FindSomethingCommand implements Callable<Integer> {

    @Option(names = {"-u", "--username"}, description = "Search for this name")
    String userName;

    @Option(names = {"-py", "--productionyear"}, description = "Search by 
    production year")
    String productionYear;

    @Option(names = {"-p", "--price"}, description = "Search for by price")
    String price;

    public Integer call() throws Exception {
        if (userName != null) {
           //do stuff with userName
        }
        if (productionYear!= null) {
           //do stuff with productionYear
        }
        if (price!= null) {
           //do stuff with price
        }
        return 0;
    }
}

I want it do be something like this:


@Command(name = "finderapp")
class FindSomethingCommand implements Callable<Integer> {

    private List<MyOption> myOptions;

    public static void addOption(MyOption myoption) {
       myOptions.add(myOption);
    }

    public Integer call() {
        myOptions.stream().forEach(o -> o.executeCall());
        return 0;
    }
}

// @OptionOfCommand annotation does not exist, this is just an example of how it could be done
@OptionOfCommand(FindSomethingCommand.class) 
public class UserNameOption implements MyOption {

   @Option(names = {"-u", "--username"}, description = "Search for this name")
    String userName;

   @Override
   public void executeCall() {
      //do something with userName
   }

}


public interface MyOption {

   public void executeCall();

}

In the terminal it then should look like this:

$finderapp -u JohnDoe -p 200 -py 2022

As you can see the problem is adding more options in the future for one command will not adhere to the SOLID principles. It should be possible to just ad a MyOption class with a new option and what it should do when called. Ultimately, if new search parameters are added there will be a long disgusting if-chain that has to be manually updated.

I would really appreciate it if anyone could help, thanks!

CodePudding user response:

I think you are looking for the @Mixin annotation.

@Command(name = "finderapp")
class FindSomethingCommand implements Callable<Integer> {

    @Mixin
    private UserNameOption userNameOption;

    @Mixin
    private ProductionYearOption productionYearOption;

    @Mixin
    private PriceOption priceOption;

    public Integer call() {
        userNameOption.executeCall();
        productionYearOption.executeCall();
        priceOption.executeCall();
        return 0;
    }
}

@Command(name = "finderapp")
class UserNameOption implements MyOption {

   @Option(names = {"-u", "--username"}, description = "Search for this name")
    String userName;

   @Override
   public void executeCall() {
      //do something with userName
   }

}

@Command(name = "finderapp")
class ProductionYearOption implements MyOption {

   @Option(names = {"-py", "--productionyear"}, description = "Search by production year")
    String productionYear;

   @Override
   public void executeCall() {
      //do something with productionYear
   }

}

@Command(name = "finderapp")
class PriceOption implements MyOption {

   @Option(names = {"-p", "--price"}, description = "Search for by price")
    String price;

   @Override
   public void executeCall() {
      //do something with price
   }

}
  • Related