I was used to subclassing the window-classes in other programming environment, but in the java tutorials I usually see something like
JPanel p = new JPanel();
p.setLayout(new BoxLayo开发者_运维百科ut(p, BoxLayout.PAGE_AXIS));
p.add(aComponent);
p.add(anotherComponent);
So what are the conventions in Java regarding subclassing top-container classes?
Use the same principle as you would for all java classes. If you are modifying or extending the behaviour or functionality, then by all means, extend JPanel
or JFrame
. The trick is to think carefully and decide if you really are adding anything. In most cases when I see people extending JFrame
it is unnecessary and wrong;
public class MyFrame extends JFrame {
public static void main(String[] args) {
new MyFrame().setVisible(true);
}
}
Why bother extending JFrame
? You haven't added anything! Composition is a much better option.
Extending JPanel
is a little different. JPanel is a fairly abstract idea, its just a generic container for other components. IMHO it is valid to subclass JPanel
to create a more concrete panel, and then use that in your application. It promotes OOP and encapsulation.
For example, say your GUI had 2 main display areas; one with some buttons/controls/inputs and another which displayed output (eg in a text area). It is perfectly acceptable to subclass JPanel
to create a ControlPanel that contains the buttons/controls/inputs. This moves all the code into a nice neat module, cleaning up your class that contains and handles the main JFrame
.
There is no rule for this, it depends how you think about your Object. You may have a JPanel which has a specific layout and bahavior, so its e.g. a VideoViewerPanel. But even this VideoViewerPanel may contain a JPanel which is only there to arrange some Buttons so you do not expicitly name it and just use it as JPanel.
I think this is related to the Liskov substitution principle, you should look into it.
Liskov substitution principle
Liskov substitution principle: Applied example
A well-known and good practice is to avoid subclassing top-level containers (JFrame
, JDialog
, JInternalFrame
).
Regarding JPanel
, several practices are in use:
- subclass it for every view (then add all components inside the subclass constructor)
- create a ViewBuilder (for each kind
of view) that dynamically adds
components to a "standard"
JPanel
I generally use the first option, which seems more logical to me, but I also sometimes use the second way, with some level of adaptation: my view builder actually creates and stores (as fields) all components but adds them to an existing panel (passed as an argument).
For example, I use that in order to reuse sets of components: e.g. I have an AddressView
class that works like that and I add it twice to a ContactView
that subclasses JPanel
, once for home address, once for office address.
One may say that I could also subclass JPanel
for AddressView
and then add 2 instances to my ContactView
panel. The reason I don't do that is because Swing LayoutManager
s don't support alignment of components across different panels, thus the resulting ContactView panel is not visually pleasing in this case.
My general rule of thumb: Before using inheritance, consider if composition makes more sense.
Reason: Subclassing usually means more complexity and connectedness, i.e. harder to change, maintain, and scale without making mistakes.
A much more complete and concrete answer from Tim Boudreau of Sun:
Common problems to the use of inheritance as I see it are:
- Innocent acts can have unexpected results - The classic example of this is calls to overridable methods from the superclass constructor, before the subclasses instance fields have been initialized. In a perfect world, nobody would ever do that. This is not a perfect world.
- It offers perverse temptations for subclassers to make assumptions about order of method calls and such - such assumptions tend not to be stable if the superclass may evolve over time. See also my toaster and coffee pot analogy.
- Classes get heavier - you don't necessarily know what work your superclass is doing in its constructor, or how much memory it's going to use. So constructing some innocent would-be lightweight object can be far more expensive than you think, and this may change over time if the superclass evolves
- It encourages an explosion of subclasses. Classloading costs time, more classes costs memory. This may be a non-issue until you're dealing with an app on the scale of NetBeans, but there, we had real issues with, for example, menus being slow because the first display of a menu triggered massive class loading. We fixed this by moving to more declarative syntax and other techniques, but that cost time to fix as well.
- It makes it harder to change things later - if you've made a class public, swapping the superclass is going to break subclasses - it's a choice which, once you've made the code public, you're married to. So if you're not altering the real functionality to your superclass, you get much more freedom to change things later if you use, rather than extend the thing you need. Take, for example, subclassing JPanel - this is usually wrong; and if the subclass is public somewhere, you never get a chance to revisit that decision. If it's accessed as JComponent getThePanel() , you can still do it (hint: expose models for the components within as your API).
- Object hierarchies don't scale (or making them scale later is much harder than planning ahead) - this is the classic "too many layers" problem. I'll go into this below, and how the AskTheOracle pattern can solve it (though it may offend OOP purists).
...
My take on what to do, if you do allow for inheritance, which you may take with a grain of salt is:
- Expose no fields, ever, except constants
- Methods shall be either abstract or final
- Call no methods from the superclass constructor
...
all of this applies less to small projects than large ones, and less to private classes than public ones
精彩评论