Skip to content

Instantly share code, notes, and snippets.

@Jimexist
Last active April 8, 2016 03:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jimexist/42a71adb2c05ff18f0832a3d598293c8 to your computer and use it in GitHub Desktop.
Save Jimexist/42a71adb2c05ff18f0832a3d598293c8 to your computer and use it in GitHub Desktop.
Java short tutorials

Design for reuse

You should aim to design for reuse, otherwise, hide it!

The reasoning behind this is that your users, including yourself in future, will naturally ignore any documentation and convention that you put in code, and use it the way they like.

If you design the class, you should aim for reusability and otherwise hide them, leaving yourself with leeway to redo.

Less is more

There is a good rule of thumb to remember: when you put a method in an interface, it's permanent, so make sure it's clear (so that users know what it means) and also easy to use (so that users can not easily misuse it).

When you are not sure, don't put anything more to an interface - it's always easy to add to it, but it's almost imposible to remove anything from an interface.

On the other hand, you can create and put private methods much more easily.

Minimal protected methods

protected methods should be place with very good reasons

Consider this class:

public class MyServer {

  /**
   * protected methods because i don't want my user to call it but i can override it later
   * 
   * NOTE: it will return null when config is not valid!
   */
  protected Config validateSettings() {
    if (this.validateDatabaseSettings()) {
      return this.validateServerSettings();
    } else {
      return null;
    }
  }
  
  protected Config validateServerSettings() {
    return Config.parseFrom(CONFIG_PATH);
  }
  
  protected void checkDatabaseConnection() {
    // omitted
  }
  
  protected boolean validateDatabaseSettings() {
    // omitted
  }
  
  protected void listenToPort(int port) {
    // omitted
  }
  
  protected void startUp() {
    Config config = this.validateSettings();
    if (config != null) {
      this.listenToPort(config.port);
    } else {
      throw new StartUpException();
    }
  }

}

There are so many protected methods, and you rely on them to be called in specific order.

But when someone extends your class to include a redis cache, it's hard to do it right:

public class MyCachingServer extends MyServer {
  
  private RedisConfig redisConfig;
  
  void validateRedisConfig() {
    this.redisConfig = RedisConfig.parse(CONFIG_PATH);
  }
  
  private void connectToRedis() {
    // omitted
  }
  
  protected void listenToPort(int port) {
    this.connectToRedis();
    super.listenToPort(port);
  }
  
  @Override
  public void startUp() {
    this.validateRedisConfig();
    // should i do this?
    super.startUp();
  }
}

It is VERY HARD to reason about the correctness and the right order / which ones to override protected methods. Not to mention things can be harder when it comes to thread safety.

A better way

A better way is to embrace prefer composition than inheritance. A good example is Guava's Forwarding Collections.

By subclassing ForwardingXXX and implementing the delegate() method, you can override only selected methods in the targeted class, adding decorated functionality without having to delegate every method yourself.

Promote composibility

Composibility is a good metrics for code reuse. When possbile, always try to make a piece of code composible.

Consider this example:

public class Person {
  
  private final String firstName, lastName;
  
  // ctor, getters omitted
  
  public String toJson() {
    return new StringBuilder()
      .append("{")
      .append("\"first_name\"").append(firstName)
      .append(',')
      .append("\"last_name\"").append(lastName)
      .append("}")
      .toString();
  }

}

But what if there's a need for toJsonPretty?

  public String toJson(boolean pretty) {
    // omitted if-else
  }

Ok, but what about there's a need for specifying level of indentation being 2 or 4?

  public String toJson(boolean pretty, int indentation) {
    // omitted if-else and then if else
  }

Does it work? Not quite - what if there's a need of listOfPeople.toJson? How do you tell the indentation from within Person class? What about type List<Map<String, Person>>?

Make it composible!

The problem is that String is quite a composible type.

Consider the following:

public class Person {
  
  public Map<String, Object> toMap() {
    return ImmutableMap.builder()
      .put("first_name", firstName)
      .put("last_name", lastName);
      .build();
  }

}

and then

public class JsonSerializer {
  
  public String listToJson(List<Object> list, boolean pretty, int indentationLevel) {
    // you can call mapToJson from here
  }
  
  public String mapToJson(Map<String, Object> map, boolean pretty, int indentationLevel) {
  
  }
  
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment