wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Builder pattern with flexible defaults

Hero image for Builder pattern with flexible defaults

objects in real life are complex with many optionalities (my spell checker claims that isn't a word), that leave their imprint on their digital twins. To avoid exposing endless list of parameters you can use the builder pattern described by the GoF. It is the digital version of

This is what I know, let's work with that

You might want to go with the flow, and just pick a size and for the rest pick what everyone else is picking. The builder pattern does both gracefully.

Ring r1 = Ring.builder().size(9)build();

Ring r2 = Ring.builder()
              .material(Material.PLATINUM)
              .size(9)
              .diamonds(3)
              .carats(0.75)
              .engraving("forever")
              .build();

or if Rust rocks your boat

let r3 = RingBuilder::builder()
                     .material(Material::Platinum)
                     .size(9)
                     .diamonds(3)
                     .carats(0.75)
                     .engraving("forever")
                     .build();

So I was wondering what it would take using defaults from a configuration file instead hard coding them in the builder.

Turns out, not much (using Quarkus):

@Inject
RingDefaults defaults;

Ring r3 = Ring.builder(defaults).size(9)build();

with Micrprofile config doing the heavy lifting.

@JsonIgnoreProperties(ignoreUnknown = true)
public record RingDefaults(
        Material material,
        Integer size,
        Integer diamonds,
        Double carats,
        String engraving) {

    public static final RingDefaults NONE = new RingDefaults(null, null, null, null, null);
}

public class RingDefaultsProducer {

    @Produces
    @Singleton
    RingDefaults ringDefaults(
            ObjectMapper mapper,
            @ConfigProperty(name = "wr.defaults.path") Path path) {
        try {
            if (!Files.exists(path)) {
                return RingDefaults.NONE;
            }
            var json = Files.readString(path);
            return json.isBlank() ? RingDefaults.NONE : mapper.readValue(json, RingDefaults.class);
        } catch (IOException e) {
            throw new UncheckedIOException("Cannot read ring defaults from " + path, e);
        }
    }
}

Rust takes a similar approach

use derive_builder::Builder;
use serde::Deserialize;
use std::{error::Error, fs, io, path::Path};

#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]

let path =  "ring_defaults.json";
let defaults = RingDefaults::load(&path)?;
let r4 = RingBuilder::from_defaults(&defaults).size(12).build();

#[derive(Debug, Builder)]
#[builder(pattern = "owned", build_fn(validate = "Self::validate"))]
pub struct Ring {
    #[builder(default = Material::Gold])
    pub material: Material,
    pub size: u8,           // required: no built-in default
    #[builder(default = "0")]
    pub diamonds: u8,
    #[builder(default = "0.0")]
    pub carats: f32,
    #[builder(setter(into, strip_option), default)]
    pub engraving: Option<String>,
}

As usual YMMV


Posted by on 14 October 2026 categories: JavaRust

Add your comment…

Comments

  1. No comments yet, be the first to comment