Search code examples
javastringconcatenationcharacter

Generalize two similar methods that have different unary operators


Suppose I have the following implementations

private static String encryptPassword(String password) {
    String encryptedPassword = "";
    char[] chars = password.toCharArray();
    for (char c : chars) {
        encryptedPassword = encryptedPassword.concat(String.valueOf(++c));
    }
    return encryptedPassword;
}

private static String decryptPassword(String encryptedPassword) {
    String decryptedPassword = "";
    char[] chars = encryptedPassword.toCharArray();
    for (char c : chars) {
        decryptedPassword = decryptedPassword.concat(String.valueOf(--c));
    }
    return decryptedPassword;
}

These methods do very simple stuff that it will increase each character in a string by 1 and this process is considered as encryption. As a contrast, the process of decryption will decrease the value.

I can see that these two methods are pretty much the same, except the unary operators.

How can I generalize these two methods?

Furthermore, I also prefer streaming processing to looping. If you have any ideas regarding this point, please posting as well.


Solution

  • You can make use of the Java 8 Functional programming features to provide the behavior expressed as a Function as a method argument.

    Here's how your logic can be implemented using Stream API, the additional argument the method expects is of type IntUnaryOperator (which is a function expecting a single int argument, and producing int value as the result).

    public static String processPassword(String password,
                                         IntUnaryOperator operator) {
        return password.chars()
            .map(operator)
            .collect(
                StringBuilder::new,
                StringBuilder::appendCodePoint,
                StringBuilder::append
            )
            .toString();
    }
    

    And here's how you can call this method from the client code:

    String encrypted = processPassword("fooBar", i -> ++i);
    System.out.println(encrypted);
    String decrypted = processPassword(encrypted, i -> --i);
    System.out.println(decrypted);
    

    Output:

    gppCbs
    fooBar
    

    But, it's error-prone (for instance, a function with ++i can be accidentally instead of a function with --i), and has a maintainability issue because you might end up with repeating these functions in several places, so it would be harder to change them.

    The better approach would be to abstract the client from the actual implementations of the functions used in encrypting/decrypting and store these functions in one place, so that they can be easily modified without effecting the rest of the code.

    This can be achieved by introducing an enum.

    public enum ProcessType {
        ENCRYPT(i -> ++i),
        DECRYPT(i -> --i);
        
        private IntUnaryOperator operator;
    
        // constructor, getter
    }
    

    And we need to modify the method shown above to make it consume an enum constant instead of a function:

    public static String processPassword(String password,
                                         ProcessType type) {
        return password.chars()
            .map(type.getOperator())
            .collect(
                StringBuilder::new,
                StringBuilder::appendCodePoint,
                StringBuilder::append
            )
            .toString();
    }
    

    And that's how the client code would look like:

    String encrypted = processPassword("fooBar", ProcessType.ENCRYPT);
    System.out.println(encrypted);
    String decrypted = processPassword(encrypted, ProcessType.DECRYPT);
    System.out.println(decrypted);
    

    Output:

    gppCbs
    fooBar