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 Stephan H Wissel on 14 October 2026 categories: JavaRust