Clojure GUI 编程很难

发布于 2024-10-31 13:38:47 字数 2497 浏览 0 评论 0原文

我正在使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

陈年往事 2024-11-07 13:38:47

检查这两个在 clojure 世界中非常流行的选项:

  • CLJFX(如果您同意从 swing 迁移)到 javafx.
  • 跷跷板如果你想继续摇摆。

编辑:新鲜信息:https://tonsky.me/blog/skija/

Check these two options that are quite popular in the clojure world:

  • CLJFX if you are ok with moving from swing to javafx.
  • Seesaw if you rather continue in swing.

Edit: Fresh info: https://tonsky.me/blog/skija/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文