简体   繁体   中英

Structuring a class with optional parameters

While reading this page about the builder pattern, I noticed the class contained optional parameters.

Example:

public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

If we remove the builder pattern, so our class looks like this:

 public final class NutritionFacts
 {
        private final int servingSize;
        private final int servings;
        private final AbstractMap<String,int> optionalFacts;

        public NutritionFacts (int servingSize, int servings, optionalFacts) 
        {
            this.servingSize = servingSize;
            this.servings    = servings;
            // code to make defensive copy of optionalFacts
        }
 }

Would there be any problem or downside to taking those optional parameters and placing them inside an AbstractMap and passing it to the constructor?

The only disadvantage I can see is the work to validate it.

  1. You would have to create a defensive copy
  2. Create a private List<String> validOptionalFacts; and loop through the keys of the AbstractMap and ensure the String values are valid and if not, throw an exception.
  3. Check and throw an exception for duplicated parameters.

For this small example, it might be okay to have the optional parameters outside of a map , but suppose you had 10+ optional parameters, this would mean creating new setters/getters for those parameters, where as if it were a map, I can have a method like this:

public NutritionFacts updateParameter(String key, int value){ 
     //call method to validate the key/value
     //update fact
     //return a new object that reflects our change
     return this; 
}

public int retrieveValuefor(String key){
     //call method to validate the key
     //Get value associated with the key
     //return value associated with the key
     return factValue;
}

The map approach is bad for a few reasons:

  1. Your approach is against regular OO programming paradigms.

    • When you define an object you also define all of the possible attributes that can be assigned to it, the code that uses the object does not define characteristics of the object, it simply uses the object for its intended purpose.
  2. The code requires knowledge of the object by any code that uses the object.

  3. The code is less readable, more prone to errors, and difficult to be used in more than exactly one place.

  4. The code creates the possibility of throwing a Runtime Exception where none existed before (this alone is a reason to avoid your approach).

If I would like to use an object made by another developer I simply create an instance of that object (or perhaps I am altering code that has already created an instance of the object) and look at all the valid attributes I can set and get.
Under this map scenario am I supposed to look back since the instantiation of the object and find every alteration of the map? What if I want to add a new parameter to the map, how do I know if it is a valid parameter? How will code down the line know what mystery parameters are contained in this map?
If your solution to this problem is to define the valid map parameters inside the object, what is the point of doing this as opposed to the standard way of defining objects?

There are many more reasons why this approach would be less than desirable, but what exactly is the solution this solving? Creating fewer getters and setters? Any IDE will generate them automatically, but even if that were not the case a very slight improvement in coding time would never be worth the laundry list of problems this solution would create.

The way I see it there are some disadvantages in addition to the fact that validation might become much more cumbersome, as you already pointed out.

As noted in the comments to your question, your approach will only work if your optional parameters all have the same value. If they do not, you will have to resort to using Map<String,Object> . That in turn will make it hard to deal with these parameters, as you lost all type information.

Secondly, and I think this is the main issue with your approach: When utilizing a method such as updateParameter(String key, int value) the knowledge about which parameters the object being built requires is transferred from the builder to the client using this builder. Compare:

new Builder.updateParameter("calories", 0)
          .updateParameters("fat", 1)
          .updateParameters("carbohydrates",0)
          .build();

new Builder.calories(0)
          .fat(1)
          .carbohydrates(0)
          .build();

With the second approach the client will know that it is possible to set carbohydrates, because the builder provides a public method for it. It will not be possible to set the parameter mumbojumbo , because no such method exists.

In the first approach, will the client know if there is an additional parameter proteins ? What happens if you mistype? What happens if a provided parameter is not actually used by the object being built? All these questions don't even pose themselves when sticking to the approach without the map.

In summary: Your approach provides seemingly more convenience (at first glance) at the cost of safety, readability and maintainability.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM