Clojure GUI 编程很难
我正在使用 Swing GUI 编写一个实用程序。我正在尝试使用 Martin Fowler 的 演示模型 来促进测试。我的应用程序将使用 java.util.prefs.Preferences 自动存储多个用户首选项(即:主窗口位置和大小)。我周末花了几个小时尝试创建 Preferences
API 的 Clojure 模拟(使用 EasyMock)这样我就可以测试我的演示者代码,但无法让它工作。对于长期的 OO 程序员来说,使用非 OO 风格的 Clojure GUI 编程很困难。我觉得如果我能够发现/开发这些东西的模式(模拟、视觉“类”的“接口”等),我就可以在应用程序的其余部分继续使用相同的模式。
我也在 Scala 中开发了相同的应用程序来比较编程模式,并发现它更加直观,尽管我尝试以相当严格的函数风格使用 Scala(当然,不包括对 Java 类的调用,例如Swing API——在 Clojure 版本中也会有相同的可变性问题,但当然,它也是单线程的)。
在我的 Scala 代码中,我创建了一个名为 MainFrame
的类,它扩展了 JFrame
并实现了 MainView
特征。 MainView
将所有 JFrame
调用公开为我可以在模拟对象中实现的抽象方法:
trait LabelMethods {
def setText(text: String)
//...
}
trait PreferencesMethods {
def getInt(key: String, default: Int): Int
def putInt(key: String, value: Int)
//...
}
trait MainView {
val someLabel: LabelMethods
def addComponentListener(listener: ComponentListener)
def getLocation: Point
def setVisible(visible: Boolean)
// ...
}
class MainFrame extends JFrame with MainView {
val someLabel = new JLabel with LabelMethods
// ...
}
class MainPresenter(mainView: MainView) {
//...
mainView.addComponentListener(new ComponentAdaptor {
def componentMoved(ev: ComponentEvent) {
val location = mainView.getLocation
PreferencesRepository.putInt("MainWindowPositionX", location.x)
PreferencesRepository.putInt("MainWindowPositionY", location.y)
}
mainView.someLabel.setText("Hello")
mainView.setVisible(true)
}
class Main {
def main(args: Array[String]) {
val mainView = new MainFrame
new MainPresenter(mainView)
}
}
class TestMainPresenter {
@Test def testWindowPosition {
val mockPreferences = EasyMock.mock(classOf[PreferencesMethods])
//... setup preferences expectation, etc.
PreferencesRepository.setInstance(mockPreferences)
val mockView = EasyMock.createMock(classOf[MainView])
//... setup view expectations, etc.
val presenter = new MainPresenter(mockView)
//...
}
}
我正在使用伪单例(包括 setInstance
以便模拟可以替换首选项的“真实”版本),因此未显示详细信息。我知道蛋糕图案,但发现我的在这种情况下更容易使用。
我一直在努力在 Clojure 中编写类似的代码。有没有做这种事情的开源项目的好例子?我读过几本关于 Clojure 的书(Programming Clojure、The Joy of Clojure、Practical Clojure),但还没有看到这些问题的处理。我还研究了 Rich Hickey 的 ants.clj
,但他在该示例中对 Swing 的使用非常基础。
I am writing a utility program using a Swing GUI. I am trying to use Martin Fowler's Presentation Model to facilitate testing. My application will automatically store several user preferences using java.util.prefs.Preferences
(i.e.: main window position and size). I spent several hours over the weekend trying to create a Clojure mock of the Preferences
API (using EasyMock) so that I could test my presenter code, but could not get it working. Clojure GUI programming using non-OO style is hard for a long-time OO programmer. I feel that if I can discover/develop patterns for these things (mocking, "interfaces" for visual "classes", etc.), I can continue to use the same patterns throughout the rest of the application.
I have also been developing the same application in Scala to compare the programming patterns and have found it to be much more intuitive, even though I am trying to use Scala in a fairly strict functional style (excluding, of course, calls to Java classes like the Swing API -- which will have the same mutability issues in the Clojure version, but of course, will also be single-threaded).
In my Scala code, I create a class called MainFrame
that extends JFrame
and implements the trait MainView
. MainView
exposes all of the JFrame
calls as abstract methods that I can implement in a mock object:
trait LabelMethods {
def setText(text: String)
//...
}
trait PreferencesMethods {
def getInt(key: String, default: Int): Int
def putInt(key: String, value: Int)
//...
}
trait MainView {
val someLabel: LabelMethods
def addComponentListener(listener: ComponentListener)
def getLocation: Point
def setVisible(visible: Boolean)
// ...
}
class MainFrame extends JFrame with MainView {
val someLabel = new JLabel with LabelMethods
// ...
}
class MainPresenter(mainView: MainView) {
//...
mainView.addComponentListener(new ComponentAdaptor {
def componentMoved(ev: ComponentEvent) {
val location = mainView.getLocation
PreferencesRepository.putInt("MainWindowPositionX", location.x)
PreferencesRepository.putInt("MainWindowPositionY", location.y)
}
mainView.someLabel.setText("Hello")
mainView.setVisible(true)
}
class Main {
def main(args: Array[String]) {
val mainView = new MainFrame
new MainPresenter(mainView)
}
}
class TestMainPresenter {
@Test def testWindowPosition {
val mockPreferences = EasyMock.mock(classOf[PreferencesMethods])
//... setup preferences expectation, etc.
PreferencesRepository.setInstance(mockPreferences)
val mockView = EasyMock.createMock(classOf[MainView])
//... setup view expectations, etc.
val presenter = new MainPresenter(mockView)
//...
}
}
I am using a pseudo-singleton (setInstance
included so that a mock can replace the "real" version) for the Preferences, so the details are not shown. I know about the cake pattern, but found mine to be a little easier to use in this case.
I have struggled with doing the similar code in Clojure. Are there any good examples of open-source projects that do this kind of thing? I have read several books on Clojure (Programming Clojure, The Joy of Clojure, Practical Clojure), but have not seen these issues dealt with. I have also studied Rich Hickey's ants.clj
, but his use of Swing in that example is pretty basic.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
检查这两个在 clojure 世界中非常流行的选项:
编辑:新鲜信息:https://tonsky.me/blog/skija/
Check these two options that are quite popular in the clojure world:
Edit: Fresh info: https://tonsky.me/blog/skija/