I'm creating a library, and I want there to be an abstract class called Optimizer. The user should be able to choose from a set of predefined Optimizers and if desired, use fluent methods to change certain fields.
Every Optimizer has a "learning rate", so I've defined learningRate as a field within that class as well as its fluent setter.
abstract class Optimizer {
private double learningRate;
public Optimizer withLearningRate(double learningRate) {
this.learningRate = learningRate;
return this;
}
}
One type of Optimizer should be "Gradient Descent" (which has a default learning rate of 0.01 + more properties than just learning rate), so I defined OptimizerGD accordingly:
public class OptimizerGD extends Optimizer {
private double momentum = 0; // default momentum
private boolean nesterov = false; // default nesterov
public OptimizerGD() {
withLearningRate(0.01); // default learning rate
}
public OptimizerGD withMomentum(double momentum) {
this.momentum = momentum;
return this;
}
public OptimizerGD withNesterov() {
this.nesterov = true;
return this;
}
}
And I want the user to be able to select this Optimizer in a simple way, like Optimizer.GD, so I added this line to my Optimizer class:
public static final OptimizerGD GD = new OptimizerGD();
Allowing for:
model.compile().withOptimizer(GD.withNesterov());
Here are my concerns with my current design pattern, and any ideas I have for possible solutions:
- IntelliJ warns me that "Referencing subclass OptimizerGD from superclass Optimizer initializer might lead to class loading deadlock".
My idea for this is to make another class called Optimizers to contain the static field for GD (and other ones I add later), although I think I would prefer to keep this stuff in Optimizer because that seems more conventional? I suppose this doesn't make too much of a difference from the user's POV though.
- My fluent setter for learning rate returns
Optimizerinstead of the specific subtype ofOptimizer, meaning that it always has to be called last when I chain setters (GD.withNesterov().withLearningRate(0.5))
My idea for this is to remove withLearningRate from Optimizer, and instead have it implemented in each subclass I make so that it can return the subtype. The reason I hesitate to embrace this is because I'm repeating the same code with just a different return type.
- I don't want the user to be able to implement their own
Optimizeror instantiateOptimizerGD. I want them to always useGDfollowed by any setters they may wish to use. I believe I've preventedOptimizerimplementations by making it package-private, butOptimizerGDhas to be public, it seems, for the setters to be accessible.
Don't know what to do here. Maybe my design pattern needs to use enums?
I've asked several things here but they're all connected to my one main question, which is what the design pattern of my code should be given my goals and specifications.