Java DRY 无泄漏函数

Java DRY without leaking functions

提问人:404 Name Not Found 提问时间:10/3/2023 更新时间:10/3/2023 访问量:62

问:

假设我有几个类是“弱单例”——不应该初始化多次的东西(注册侦听器等),但无论出于何种原因,我都不能使它们成为真正的单例甚至静态类,所以我能做的最好的事情就是警告自己(或者抛出一个我猜,但出于这个问题的目的,我如何处理它并不重要)。RuntimeException

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;
    }
}

有几个这样的“弱单例”,所以用手打字会很累。另外,我还觉得应该有更好的方法来做我的检查,以免重复我自己。

public class FooWidget {
    private static boolean initialized = false;

    // stuff

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

public class BarWidget {
    // this gets tiring
}

接口中的私有函数不起作用 - 使其中的函数仅在接口定义中可用。private

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有效,但它到处暴露它。

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" ...
    }
}

从表面上看,一个抽象类为我节省了定义布尔值并检查它的过程:

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();
    }
}

不幸的是,它以其他方式中断:

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

撇开有问题的设计不谈,有没有办法在不泄露检查功能的情况下实现我的目标,即减少检查所需的打字量?

感谢您抽出宝贵时间接受采访。

爪哇

评论

5赞 Jorn 10/3/2023
帧质询 - 您无需在对象内部检查此内容。只需确保初始化代码(例如 DI 框架)仅提供每个对象的单个实例即可。大多数 DI 框架都允许您将某些内容绑定为单例。
0赞 404 Name Not Found 10/3/2023
@Jorn我想我应该提一下,这是我在实际项目中遇到的问题的简化示例,这是一个 Minecraft 模组。我不确定我可以在这种情况下使用什么(如果有的话)DI 框架 - 因此我手动处理了所有内容。
1赞 Endzeit 10/3/2023
除了@Jorn说的,如果你不想集成一个完整的 DI 框架,你也可以使用工厂模式来集中逻辑,以提供相关类的单例对象。使用工厂还可以避免不同类的重复。
1赞 rzwitserloot 10/3/2023
您的检查在多线程环境中不起作用(想象一下同时有 2 个线程。他们俩都没有警告,然后他们都设置了.一个简单的解决方法是使用 for ,并在末尾发出警告(尝试设置为 .如果已经存在,请发出警告)。new X()initialized = trueAtomicBooleaninitializedinitializedtrue
0赞 biziclop 10/3/2023
@rzwitserloot 由于您只将标志设置为 true,因此在这里也有效。volatile

答:

-1赞 Cactusroot 10/3/2023 #1

您可以创建构造函数 ,类并创建一个实例:privatefinal

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

    private WidgetManager() {
    }
}

但我认为你不需要为侦听器设置单例,只需每次创建一个新实例,即使这些实例没有任何不同。

评论

0赞 Jorn 10/3/2023
如果你的单例需要自己的依赖项,这将不起作用
0赞 Cactusroot 10/3/2023
@Jorn 你是说循环引用吗?
0赞 Jorn 10/3/2023
不一定,任何依赖项都可以。因为当您初始化类(因此 .INSTANCE
0赞 Cactusroot 10/3/2023
@Jorn 如果要初始化的类具有依赖关系,但在编译时不知道这些依赖关系是什么,则可能不应使用单一实例模式。
0赞 Cactusroot 10/3/2023
@Jorn 如果 WidgetManager 在编译时知道它依赖于什么,例如 FooWidget 和 BarWidget,它可以简单地引用它们的 INSTANCE 字段,类加载器将首先加载它们,然后再加载 WidgetManager。我可能没有正确理解你。