什么时候应该扩展Java Swing类?


35

我对继承实现的当前理解是,只有存在IS-A关系时,才应该扩展类。如果父类可以进一步具有具有不同功能的更特定的子类型,但将共享在父类中抽象的公共元素。

我质疑这种理解,因为我的Java教授建议我们这样做。他建议JSwing我们在课堂上建立一个应用程序

一个都应该延伸JSwing类(JFrameJButtonJTextBox等)到单独的自定义类和在其中指定GUI相关的定制(例如部件尺寸,部件的标签,等)

到目前为止还算不错,但是他进一步建议每个JButton应该有自己的自定义扩展类,即使唯一的区别是标签。

例如,如果GUI有两个按钮,则“ 确定”和“ 取消”。他建议应将它们扩展如下:

class OkayButton extends JButton{
    MainUI mui;
    public OkayButton(MainUI mui) {
        setSize(80,60);
        setText("Okay");
        this.mui = mui;
        mui.add(this);        
    }
}

class CancelButton extends JButton{
    MainUI mui;
    public CancelButton(MainUI mui) {
        setSize(80,60);
        setText("Cancel");
        this.mui = mui;
        mui.add(this);        
    }
}

如您所见,唯一的区别在于setText功能。

那么这是标准做法吗?

顺便说一句,讨论该课程的课程称为Java最佳编程实践

[教授回覆]

因此,我与教授讨论了这个问题,并提出了答案中提到的所有观点。

他的理由是子类在遵循GUI设计标准的同时提供了可重用的代码。例如,如果开发人员在一个窗口中使用了自定义OkayCancel按钮,则将相同的按钮放置在其他Windows中也将变得更加容易。

我知道了我想起的原因,但它仍然只是在利用继承并使代码易碎。

后来,任何开发人员可以无意中调用setText上的Okay按钮,并改变它。在这种情况下,子类变得很麻烦。


3
JButton当您可以简单地创建一个JButton并在类外部调用相同的公共方法时,为什么还要在其构造函数中扩展并调用公共方法呢?

17
如果可以的话,请远离那个教授。这远非最佳实践。如您所说明的代码重复是一种非常难闻的气味。
njzk2

7
只是问他为什么,不要犹豫在这里传达他的动机。
亚历克斯

9
我想投票,因为那是糟糕的代码,但我也想投票,因为这是很棒的,您可以提问而不是一味地接受不良教授的言论。
尼克·哈特利

1
@Alex我已经用教授的理由更新了这个问题
Paras

Answers:


21

这违反了《里斯科夫替代原则》,因为OkayButton在任何Button预期的地方都不能替代a 。例如,您可以根据需要更改任何按钮的标签。但是这样做OkayButton违反了其内部不变性。

这是经典的继承滥用,用于代码重用。请改用辅助方法。

不这样做的另一个原因是,这只是实现线性代码所能达到的目标的复杂方法。


除非LSP OkayButton具有您要考虑的不变式,否则它不会违反LSP 。该OkayButton可以要求不要有任何额外的不变量,它可能只考虑文本作为默认,并打算全力支持修改文本。仍然不是一个好主意,但不是出于这个原因。
hvd

它不会违反它,因为您可以。即,如果某个方法activateButton(Button btn)需要一个按钮,则可以轻松为其指定一个实例OkayButton。该原理并不意味着它必须具有完全相同的功能,因为这会使继承变得毫无用处。
塞布'16

1
@Sebb,但是您不能将其与函数一起使用,setText(button, "x")因为它违反了(假定的)不变式。确实,这个特定的OkayButton可能没有任何有趣的不变式,所以我想我选了一个不好的例子。但这可能。例如,如果点击处理程序说了if (myText == "OK") ProcessOK(); else ProcessCancel();怎么办?然后,文本是不变量的一部分。
usr

@usr我没有想到文本是不变式的一部分。您说的没错,那就违反了。
塞布'16

50

它以各种可能的方式都是完全可怕的。在大多数,使用工厂函数产生Jbutton将。仅当您有严重的扩展需求时才应从它们继承。


20
+1。有关更多参考,请参见AbstractButton的 Swing类层次结构。然后查看JToggleButton的层次结构。继承用于概念化不同类型的按钮。但这并不是用来区分业务概念的是,否,继续,取消...)。
2016年

2
@Laiv:即使具有不同的类型(例如JButton,,JCheckBoxJRadioButton,也只是让开发人员熟悉其他工具箱(例如普通的AWT),在这种情况下这些类型是标准的。原则上,它们都可以由单个按钮类处理。是模型和UI委托的组合,从而产生了实际的差异。
Holger

是的,它们可以通过一个按钮来处理。但是,可以将实际层次结构作为良好实践来公开。创建一个新的Button或仅将事件和behaivor的控件委派给另一个组件是首选项的问题。如果是我,我将扩展Swing组件以对owm按钮进行建模,并封装其行为,动作等……只是为了简化其在项目中的实现并使初级用户容易。在Web env中,相对于太多的事件处理,我更喜欢Web组件。无论如何。没错,我们可以将UI与Behaivors分离。
Laiv

12

这是编写Swing代码的非常不标准的方法。通常,您很少会创建Swing UI组件的子类,最常见的是JFrame(以设置子窗口和事件处理程序),但是即使子类也是不必要的,许多人也不希望这样做。通常通过扩展AbstractAction类(或它提供的Action接口)来为按钮提供文本自定义功能。这可以提供文本,图标和其他必要的视觉自定义,并将它们链接到它们代表的实际代码。与您显示的示例相比,这是编写UI代码的更好的方法。

(顺便说一下,谷歌学者从未听说过您引用的论文 -您有更准确的参考文献吗?)


“标准方式”到底是什么?我同意为每个组件编写一个子类可能不是一个好主意,但是Swing官方教程仍然支持这种做法。我认为教授只是遵循框架官方文档中显示的样式。

@COMEFROM我承认没有阅读整个Swing教程,所以也许我错过了使用这种样式的地方,但是我看过的页面倾向于使用基类而不是驱动子类,例如docs.oracle .com / javase / tutorial / uiswing / components / button.html
Jules,

@Jules对困惑感到抱歉,我的意思是教授所教授的课程/论文称为Java最佳实践
Paras,2016年

1
通常是正确的,但还有另一个主要例外- JPanel。子类化实际上比子类化有用JFrame,因为面板只是一个抽象容器。
Ordous

10

恕我直言,Java最佳编程实践由Joshua Bloch的书《 Effective Java》定义。老师给您进行OOP练习非常好,学习阅读和编写其他人的编程风格也很重要。但是,在乔什·布洛赫(Josh Bloch)的书之外,关于最佳实践的观点差异很大。

如果要扩展此类,则最好利用继承。创建一个MyButton类来管理通用代码,并将其子类化为可变部分:

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

什么时候是个好主意?当您使用的类型,你已经创建了!例如,如果您具有创建弹出窗口的功能,并且签名为:

public void showPopUp(String text, JButton ok, JButton cancel)

您刚刚创建的那些类型没有任何好处。但:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

现在,您已经创建了一些有用的东西。

  1. 编译器验证showPopUp是否带有OkButton和CancelButton。有人阅读代码知道这个功能是怎样来使用,因为如果去约会的出这种文档将导致编译时错误。这是主要的好处。关于类型安全性好处的1或2个经验研究发现,对人类代码的理解是唯一可量化的好处。

  2. 这也可以防止在您反转传递给该功能的按钮顺序时出错。这些错误很难发现,但是也很少见,因此这是一个很小的好处。这曾被用来出售类型安全性,但没有经过经验证明特别有用。

  3. 该功能的第一种形式更加灵活,因为它将显示任意两个按钮。有时,这比限制将要使用的按钮类型要好。请记住,您必须在更多情况下测试“任意按钮”功能-如果仅当您正确传递正确的按钮功能时,该功能才起作用,您不会假装将使用任何按钮对任何人都没有好处。

面向对象编程的问题在于,它把购物车放在了马的前面。您需要首先编写函数,以了解签名是什么,并知道是否值得为其创建类型。但是在Java中,您需要首先创建类型,以便可以编写函数签名。

因此,您可能需要编写一些Clojure代码,以了解首先编写函数有多棒。您可以快速编写代码,并尽可能多地考虑渐近复杂性,Clojure的不变性假设可以像Java中的类型一样防止错误。

我仍然喜欢大型项目的静态类型,现在使用一些函数实用程序,这些函数使我可以先编写函数,然后再使用Java命名类型。只是一个想法。

PS我将mui指针定为最终指针-不必是可变的。


6
在您的3点中,可以使用通用JButton和适当的输入参数命名方案来处理1和3。第二点实际上是一个缺点,因为它会使您的代码更紧密地耦合在一起:如果您确实希望将按钮顺序颠倒怎么办?如果要使用“是/否”按钮代替“确定/取消”按钮怎么办?您是否将制作一个全新的showPopup方法,该方法采用YesButton和Nobutton?如果仅使用默认JButton,则无需先创建类型,因为它们已经存在。
Nzall '16

扩展@NateKerkhofs所说的,现代IDE将在您输入实际表达式时为您显示正式的参数名称。
所罗门慢

8

我愿意打赌,您的老师实际上并不相信您应该始终扩展Swing组件来使用它。我敢打赌他们只是以此为例来强迫您练习扩展类。我现在不会太担心现实世界中的最佳实践。

话虽这么说,在现实世界中,我们更倾向于组合而不是继承

您的规则“只有在存在IS-A关系的情况下才应扩展类”的规则不完整。它应以粗体大写字母“ ...,并且我们需要更改类的默认行为”结尾。

您的示例不符合该条件。您不必仅extendJButton班级上设置其文字。您不必仅extendJFrame类中添加组件。您可以使用它们的默认实现很好地完成这些操作,因此添加继承只会增加不必要的复杂性。

如果我看到一个班级extends又一个班级,那我会怀疑该班级正在发生什么变化。如果您什么都没做,就不要让我完全看完课。

回到您的问题:什么时候应该扩展Java类?当您有一个非常非常非常好的扩展课程的理由时。

这是一个特定的示例:一种进行自定义绘画(用于游戏或动画,或仅用于自定义组件)的方法是扩展JPanel类。(对更多的信息在这里。)你延长JPanel,因为你需要的类重写paintComponent()功能。您实际上是通过这样做来更改类的行为。您无法使用默认JPanel实现进行自定义绘画。

但是就像我说的那样,您的老师可能只是以这些例子为借口,迫使您练习扩展课堂。


1
哦,等等,您可以设置一个Border可以绘制其他装饰的图形。或创建一个JLabel并将自定义Icon实现传递给它。没有必要JPanel为绘画子类化(以及为什么JPanel代替JComponent)。
霍尔格

1
我认为您在这里有正确的想法,但可能可以选择更好的例子。

1
但是我确实想重申您的回答是正确的,并且您也提出了很好的观点。我只是认为特定示例和教程对此支持不强。

1
@KevinWorkman我不了解Swing游戏的开发,但是我已经在Swing中完成了一些自定义UI,并且“不扩展Swing类,因此很容易通过配置连接组件和渲染器”在那是相当标准的。

1
“我敢打赌他们只是以此为例强迫您...”我的一大怒气!谁教教师如何使用的可怕失常的例子做X 为什么做X.
所罗门慢
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.