Java(FX) - 只允许1个类从单例类调用方法

时间:2015-01-23 15:53:51

标签: java methods javafx singleton

我目前正在开发一个项目,我正在使用单例类来更改用户的视图。此DisplayManager(单例)具有addView(View view)replaceView(View view)等方法。但它也有一个名为displayRootView()的方法,它只能被调用一次(在init期间)并且只能被调用一个类,即扩展Application类的StartUp类。

是否可以阻止使用单例的其他类调用displayRootView()方法?

我已经考虑过StackTrace,但这似乎并不理想。我可能通过在StartUp类上使用标记接口将其与其余类分开来?

任何建议都将不胜感激。

4 个答案:

答案 0 :(得分:3)

呃,这很难阻止某些类调用你的方法,因为它打破了一些核心的OOP原则。该方法不应该关心调用它。它是关注点的基本分离 - 你的方法应该明确地约定它的作用,而不是当时JVM的状态。

考虑以下问题:

  • 如果您继承StartUp会怎样?例如,分离桌面,移动和Web平台?
  • 如何在不涉及StartUp的情况下对此方法进行单元测试?
  • 如果你需要另一层抽象会怎么样?
  • 如果您何时以及何时添加代理(通过AOP或Spring代理)?
  • 如果您需要从Timer调用该方法,会发生什么?它仍将从StartUp类源调用(并且是正确的),但它不会出现在堆栈跟踪中。

等其他考虑因素。

抛出某种异常(如IllegalStateException或自定义异常),以防第二次调用该方法绝对有效恕我直言。

这看起来可能需要对代码进行静态检查,而不是代码内或运行时检查。我不认为向Findbugz或PMD添加自定义规则以查找方法的所有直接调用并检查调用类(如果从其他位置调用构建失败),那将非常困难。但我不认为这样的检查实际上是有用的。

最后,你需要在所述类之外合法使用该方法,而不是有人在被警告并且已经创建了适当的Javadoc之后不正确地使用它。

答案 1 :(得分:2)

JavaFX Application source通过解析堆栈跟踪以确定并检查调用者来做一些时髦的事情(例如,确保它只从扩展Application的类中调用)。

/**
 * Launch a standalone application. This method is typically called
 * from the main method(). It must not be called more than once or an
 * exception will be thrown.
 * This is equivalent to launch(TheClass.class, args) where TheClass is the
 * immediately enclosing class of the method that called launch. It must
 * be a subclass of Application or a RuntimeException will be thrown.
 *
 * <p>
 * The launch method does not return until the application has exited,
 * either via a call to Platform.exit or all of the application windows
 * have been closed.
 *
 * <p>
 * Typical usage is:
 * <ul>
 * <pre>
 * public static void main(String[] args) {
 *     Application.launch(args);
 * }
 * </pre>
 * </ul>
 *
 * @param args the command line arguments passed to the application.
 *             An application may get these parameters using the
 *             {@link #getParameters()} method.
 *
 * @throws IllegalStateException if this method is called more than once.
 */
public static void launch(String... args) {
    // Figure out the right class to call
    StackTraceElement[] cause = Thread.currentThread().getStackTrace();

    boolean foundThisMethod = false;
    String callingClassName = null;
    for (StackTraceElement se : cause) {
        // Skip entries until we get to the entry for this class
        String className = se.getClassName();
        String methodName = se.getMethodName();
        if (foundThisMethod) {
            callingClassName = className;
            break;
        } else if (Application.class.getName().equals(className)
                && "launch".equals(methodName)) {

            foundThisMethod = true;
        }
    }

    if (callingClassName == null) {
        throw new RuntimeException("Error: unable to determine Application class");
    }

    try {
        Class theClass = Class.forName(callingClassName, true,
                           Thread.currentThread().getContextClassLoader());
        if (Application.class.isAssignableFrom(theClass)) {
            Class<? extends Application> appClass = theClass;
            LauncherImpl.launchApplication(appClass, args);
        } else {
            throw new RuntimeException("Error: " + theClass
                    + " is not a subclass of javafx.application.Application");
        }
    } catch (RuntimeException ex) {
        throw ex;
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

LauncherImpl代码在私有静态AtomicBoolean上使用getAndSet操作,以确保应用程序不会多次启动。

// Ensure that launchApplication method is only called once
private static AtomicBoolean launchCalled = new AtomicBoolean(false);

/**
 * This method is called by the standalone launcher.
 * It must not be called more than once or an exception will be thrown.
 *
 * Note that it is always called on a thread other than the FX application
 * thread, since that thread is only created at startup.
 *
 * @param appClass application class
 * @param preloaderClass preloader class, may be null
 * @param args command line arguments
 */
public static void launchApplication(final Class<? extends Application> appClass,
        final Class<? extends Preloader> preloaderClass,
        final String[] args) {

    if (launchCalled.getAndSet(true)) {
        throw new IllegalStateException("Application launch must not be called more than once");
    }

    if (! Application.class.isAssignableFrom(appClass)) {
        throw new IllegalArgumentException("Error: " + appClass.getName()
                + " is not a subclass of javafx.application.Application");
    }

    if (preloaderClass != null && ! Preloader.class.isAssignableFrom(preloaderClass)) {
        throw new IllegalArgumentException("Error: " + preloaderClass.getName()
                + " is not a subclass of javafx.application.Preloader");
    }

    // Create a new Launcher thread and then wait for that thread to finish
    final CountDownLatch launchLatch = new CountDownLatch(1);
    Thread launcherThread = new Thread(new Runnable() {
        @Override public void run() {
            try {
                launchApplication1(appClass, preloaderClass, args);
            } catch (RuntimeException rte) {
                launchException = rte;
            } catch (Exception ex) {
                launchException =
                    new RuntimeException("Application launch exception", ex);
            } catch (Error err) {
                launchException =
                    new RuntimeException("Application launch error", err);
            } finally {
                launchLatch.countDown();
            }
        }
    });
    launcherThread.setName("JavaFX-Launcher");
    launcherThread.start();

    // Wait for FX launcher thread to finish before returning to user
    try {
        launchLatch.await();
    } catch (InterruptedException ex) {
        throw new RuntimeException("Unexpected exception: ", ex);
    }

    if (launchException != null) {
        throw launchException;
    }
}

所以它有点复杂和怪异,但是如果你想要一个适用于JavaFX代码库的成熟解决方案,你可以尝试解析它以了解正在发生的事情并使其适应你的情况。

我只是说如果这对您的应用程序来说非常重要,那么只会为您的应用程序引入这种额外的复杂性。

Orodous为单元测试提供了一些阻碍这种逻辑的优点。例如,看一下JavaFX application的单元测试部分的建议。由于启动器中的奇怪检查,为了独立测试其应用程序的功能,开发人员需要通过奇怪的箍来绕过调用任何启动器代码(例如,使用基于Swing的JFXPanel而不是应用程序来初始化JavaFX,因为应用程序只能启动一次。

答案 2 :(得分:0)

当多次调用时,我会在displayRootView()中抛出IllegalStateException。

答案 3 :(得分:0)

您可以考虑使用&#34;罗密欧与朱丽叶&#34;给出here的技巧,最初是为了模拟朋友&#34; Java中的机制(来自C ++)。