DEV Community

Mihai Bojin
Mihai Bojin

Posted on • Originally published at mihaibojin.com on

2 2

Aiming for object-oriented design elegance

🔔 This article was originally posted on my site, MihaiBojin.com. 🔔


One of my goals for this project was to create a library that is elegant and easy to use while feeling Java idiomatic.

Let's talk SOLID. They're a set of five principles aimed at making object-oriented design implementations flexible and maintainable. I am designing a library that I hope will be a joy to use and will make developers want to adopt it, for which reason I interpreted these principles in the best possible form I could think of.

My initial thought was to offer a base Prop interface, abstracting away lower-level implementation details that are not relevant to users of this class.

However, I settled on using a few abstract classes, for several reasons, inspired by a few of the SOLID principles:

  • all Prop objects need a few common traits
  • a class should have a single responsibility; since I was building multiple themes, sticking them all into a single class didn't feel elegant
  • it should be easy to build on top of each layer
  • not all methods need to be exposed in the final public contract; unfortunately, Java interfaces do not support non-public methods

Let's break it down; here is the high-level end-result class design:

@FunctionalInterface
public interface Subscribable<T> {
  void subscribe(Consumer<T> onUpdate, Consumer<Throwable> onError);
}

public abstract class SubscribableProp<T> implements Subscribable<T> {
  /* Processes a value update event. */
  protected void onValueUpdate(@Nullable T value, long epoch) {}
  /* Processes an exception encountered during an update. */
  protected void onUpdateError(Throwable error, long epoch) {}
}

public abstract class Prop<T> extends SubscribableProp<T> implements Supplier<T> {
    /* Identifies the Prop */
    public abstract String key();

    /* Returns the Prop's value */
    public abstract T get();
}

public abstract class BoundableProp<T> extends Prop<T> {
  /* Allows the Registry to update a Prop's value */
  protected abstract boolean setValue(@Nullable String value);
}

public class Registry {
  /* Binds a Prop object to the Registry object, allowing it to process update events and set the Prop's value */
  public <T, PropT extends BoundableProp<T>> PropT bind(PropT prop) {}
}
Enter fullscreen mode Exit fullscreen mode

Subscribable denotes that a Prop can be subscribed to. The result of a prop update is either success or an error.

SubscribableProp is a partial implementation that hosts the logic necessary to process updates/errors and notify clients safely.

Prop is the absolute minimum public contract that a consumer/client should care about. It defines an identifier (key) and a way to get the prop's value.

Finally, BoundableProp encompasses all of the above and also includes a mechanism that allows the Registry to update prop values when the underlying sources are updated.

However, in practice, relying on a key and value alone, is not enough of a reason to adopt this library.

For that reason, the CustomProp class provides an almost complete implementation, par the corresponding Converter.decode() method, which requires a knowledge of the Prop's type.

public abstract class CustomProp<T> extends BoundableProp<T> implements Converter<T> {
    /* Identifies the Prop */
    public String key() {};
    /* Returns the Prop's value */
    public String get() {};
    /* Describes the prop */
    public String description() {};
    /* true, if the prop is required */
    public boolean isRequired() {};
    /* true, if the prop is a secret */
    public boolean isSecret() {};
}

@FunctionalInterface
public interface Converter<T> {
  /* Decodes a String into the desired type; must be implemented */
  T decode(@Nullable String value);

  /* Encodes the value into a String, defaulting to using Object.toString() */
  default String encode(@Nullable T value) {
    return value == null ? null : value.toString();
  }
}
Enter fullscreen mode Exit fullscreen mode

One way to extend CustomProp is to provide an implementation for Converter.decode, thus completing the class, e.g.:

public class LongProp extends CustomProp<Long> {
  public Long decode(String value) {
    Number number = safeParseNumber(value);
    try {
      return NumberFormat.getInstance().parse(value).longValue();
    } catch (ParseException e) {
      log.log(SEVERE, e);
      return null;
    }
  }    
}
Enter fullscreen mode Exit fullscreen mode

However, we can do a bit better. Since one can assume that most props will be of common Java datatypes, I have provided a series of default converters that can be composed into a final implementation. The above code can be rewritten as follows:

public class LongProp extends CustomProp<Long> implements LongConverter {  
}

public interface LongConverter extends Converter<Long> {
   @Override
   public Long decode(String value) {
    Number number = safeParseNumber(value);
    try {
      return NumberFormat.getInstance().parse(value).longValue();
    } catch (ParseException e) {
      log.log(SEVERE, e);
      return null;
    }
  }  
}
Enter fullscreen mode Exit fullscreen mode

How would we use this in production? Here's a small complete excerpt:

    Source source = new PropertyFile(PATH_TO_PROP_FILE);
    Registry registry = new RegistryBuilder(source).build();

    Prop prop = registry.bind(new LongProp("a.key"));

    prop.get(); // will return the value corresponding to a.key
    prop.subscribe(updatedValue -> {/* process updates */}, 
                   error -> {/* process any errors */});
Enter fullscreen mode Exit fullscreen mode

Hopefully, this article serves as a good high-level introduction to the contract one can expect from the props library.

In future series I'd like to explore the props library's API a bit more and show a few real-world examples of how it could be used to simplify application settings/property management in Java projects.

As always, any feedback is welcome; feel free to ping me on Twitter.

Thanks!


If you liked this article and want to read more like it, please subscribe to my newsletter; I send one out every few weeks!

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more