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 Optimizer
s 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
Optimizer
instead 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
Optimizer
or instantiateOptimizerGD
. I want them to always useGD
followed by any setters they may wish to use. I believe I've preventedOptimizer
implementations by making it package-private, butOptimizerGD
has 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.