我目前正在开发一个项目,我正在使用单例类来更改用户的视图。此DisplayManager(单例)具有addView(View view)
和replaceView(View view)
等方法。但它也有一个名为displayRootView()
的方法,它只能被调用一次(在init期间)并且只能被调用一个类,即扩展Application类的StartUp类。
是否可以阻止使用单例的其他类调用displayRootView()
方法?
我已经考虑过StackTrace,但这似乎并不理想。我可能通过在StartUp类上使用标记接口将其与其余类分开来?
任何建议都将不胜感激。
答案 0 :(得分:3)
呃,这很难阻止某些类调用你的方法,因为它打破了一些核心的OOP原则。该方法不应该关心谁调用它。它是关注点的基本分离 - 你的方法应该明确地约定它的作用,而不是当时JVM的状态。
考虑以下问题:
StartUp
会怎样?例如,分离桌面,移动和Web平台?StartUp
的情况下对此方法进行单元测试?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 ++)。