Search code examples
javadry

Java DRY without leaking functions


Suppose I have several classes that are "weak singletons" - things that shouldn't be initialized more than once (registering listeners, etc.), but for whatever reason I can't make them real singletons or even static classes, so the best I can do is warn myself (or throw a RuntimeException I guess, but for this question's purposes it doesn't matter how I handle it).

public class WidgetManager {
    private static boolean initialized = false;

    // stuff to be initialized only once

    public WidgetManager() {
        if (initialized) Main.LOGGER.warn("Instantiating multiple instances of " + getClass().getName() + "!");

        // initialize stuff

        initialized = true;
    }
}

There are several of these "weak singletons", so typing them by hand gets tiring. Plus it also feels like there should be a better way to do my checks, so as not to repeat myself.

public class FooWidget {
    private static boolean initialized = false;

    // stuff

    public FooWidget() {
        if (initialized) // ...
        // ...
    }
}

public class BarWidget {
    // this gets tiring
}

Private functions in interfaces don't work - private makes the functions in it only available within the interface definition.

public interface WeakSingleton {
    private void warnReinitialization(boolean initialized) {
        if (initialized) Main.LOGGER.warn("...");
    }
}

public class WidgetManager implements WeakSingleton {
    private static boolean initialized = false;

    public WidgetManager() {
        warnReinitialization(initialized); // <-- Error, function not visible

        initialized = true;
    }
}

default works, but it exposes it everywhere.

public interface WeakSingleton {
    default void warnReinitialization(boolean initialized) {
        // ...
    }
}

public class WidgetManager implements WeakSingleton {
    private static boolean initialized = false;

    public WidgetManager() {
        warnReinitialization(initialized); // <-- Works here

        initialized = true;
    }
}

public class Main {
    public static void main(String[] args) {
        new WidgetManager().warnReinitialization(); // <-- Oops, the interface "leaked" ...
    }
}

On the surface an abstract class saved me defining the boolean and checking it:

public abstract class WeakSingleton {
    protected static boolean initialized = false;

    protected void warnReinitialization() {
        if (initialized) System.out.println("Warning: Reinitializing " + getClass().getName() + "!");
    }
}

public class WidgetManager extends WeakSingleton {
    private FooWidget foo;
    private BarWidget bar;

    public WidgetManager() {
        warnReinitialization();
        
        System.out.println("WidgetManager ctor");

        initialized = true;
    }
}

public class FooWidget extends WeakSingleton {
    public FooWidget() {
        warnReinitialization();

        System.out.println("FooWidget ctor");

        initialized = true;
    }
}

public class BarWidget extends WeakSingleton {
    public BarWidget() {
        warnReinitialization();

        System.out.println("BarWidget ctor");

        foo = new FooWidget();
        bar = new BarWidget();

        initialized = true;
    }
}

public class Main {
    public static void main(String[] args) {
        new WidgetManager();
    }
}

Unfortunately it breaks in other ways:

user@host:~ $ javac *
user@host:~ $ java Main
WidgetManager ctor
FooWidget ctor
Warning: Reinitializing BarWidget!
BarWidget ctor
user@host:~ $

Questionable design aside, is there a way to achieve my goals of reducing the amount of typing I need for my check without leaking the checking function?

Thank you for your time.


Solution

  • You can make the constructor private, the class final and create exactly one instance:

    public final class WidgetManager {
        public static final WidgetManager INSTANCE = new WidgetManager();
    
        private WidgetManager() {
        }
    }
    

    But I don't think you need to have singletons for listeners, just create a new instance each time, even if nothing about the instances is different.