作为学习练习,现在可能是了解Model-View-Controller 的好时机。 Swing 使用一种 MVC 形式,它更像 M-VC,其中视图和控制器的绑定比纯 MVC 实现更紧密。
这里的重点是,您要将模型(数据和规则)与视图(/控制器)分开。视图/控制器成为您可以“操纵”模型的一种方式,但由于它是分离的,您可以更改模型及其工作方式而无需更改视图
您应该学习的另一个原则是编程到接口、实现的概念(请参阅Program to an Interface, Fool 和Program to an interface, not an implementation 了解一些初学者)
基本上,这会进一步解耦您的代码,这意味着您的部分代码不会对其他部分的状态或其功能做出假设,并且允许您更改物理实现(规则)而无需更改一大堆容纳它的代码。
我可能做的第一件事是创建一个interface,它描述了我希望任何人都可以从我的角色表中进行的非常基本的操作
public interface CharacterSheet {
public int getLuck();
public int getCharisma();
public int getBarter();
}
因为我特别偏执,我不明白为什么“每个人”都应该能够在他们想要的时候更改字符表,相反,我通过另一个界面限制了这一点。想想看,一旦生成了角色,你就不能在稍后的某个时间(比如在任务完成之后)更改统计数据,然后你可能想要使用一些“经验”规则/算法来分发和更新统计数据,但是那是我;)
public interface MutableCharacterSheet extends CharacterSheet {
public void setLuck(int value);
public void setCharisma(int value);
public void addPropertyChangeListener(PropertyChangeListener listener);
public void removePropertyChangeListener(PropertyChangeListener listener);
}
好的,现在我们有了更新字符表的方法。这个interface 还包括一个Observer Pattern,它可以让您检测到何时对字符表进行了更改,以便您采取适当的措施。
现在,我们需要一个实现。通常,我可能会创建一个或多个 abstract 实现来提供基本/通用功能,例如对 PropertyChangeListener 的支持,但在这种情况下,我将直接使用默认实现......
public class DefaultCharacterSheet implements MutableCharacterSheet {
private PropertyChangeSupport propertyChangeSupport;
private int luck;
private int charisma;
private int barter;
public DefaultCharacterSheet() {
propertyChangeSupport = new PropertyChangeSupport(this);
}
@Override
public int getLuck() {
return luck;
}
@Override
public void setLuck(int value) {
if (luck != value) {
int old = luck;
this.luck = value;
propertyChangeSupport.firePropertyChange("luck", old, luck);
updateBarter();
}
}
@Override
public int getCharisma() {
return charisma;
}
@Override
public void setCharisma(int value) {
if (charisma != value) {
int old = charisma;
this.charisma = value;
propertyChangeSupport.firePropertyChange("charisma", old, charisma);
updateBarter();
}
}
protected void updateBarter() {
int luck = getLuck();
int charisma = getCharisma();
int old = barter;
// Or what ever formula you want to use
barter = (int) ((luck / 2d) * charisma);
propertyChangeSupport.firePropertyChange("barter", old, barter);
}
@Override
public int getBarter() {
return barter;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
}
现在,这可能“看起来”很奇怪。我已经实现了MutableCharacterSheet,但是如果我不想让代码修改字符表怎么办?这就是 OO 的魅力所在。
不应该修改工作表的部分代码应该只支持CharacterSheet接口,这意味着我可以将DefaultCharacterSheet的实例传递给他们,但他们只会看到并能够做任何接口@ 987654340@ 允许他们这样做,Polymorphism baby!
现在,我们需要某种方式将其显示给用户并允许他们与之交互...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
MutableCharacterSheet characterSheet = new DefaultCharacterSheet();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CharacterSheetPane(characterSheet));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class CharacterSheetPane extends JPanel {
private JSpinner luckField;
private JSpinner charismaField;
private JTextField barterField;
private MutableCharacterSheet characterSheet;
public CharacterSheetPane(MutableCharacterSheet sheet) {
this.characterSheet = sheet;
characterSheet.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("barter".equals(evt.getPropertyName())) {
int value = (int) evt.getNewValue();
barterField.setText(NumberFormat.getNumberInstance().format(value));
}
}
});
luckField = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
charismaField = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
barterField = new JTextField(5);
barterField.setEditable(false);
luckField.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
characterSheet.setLuck((int) luckField.getValue());
}
});
charismaField.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
characterSheet.setCharisma((int) charismaField.getValue());
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("Luck: "), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx++;
add(luckField, gbc);
gbc.anchor = GridBagConstraints.EAST;
gbc.gridx = 0;
gbc.gridy++;
add(new JLabel("Charisma: "), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx++;
add(charismaField, gbc);
gbc.anchor = GridBagConstraints.EAST;
gbc.gridx = 0;
gbc.gridy++;
add(new JLabel("Barter: "), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx++;
add(barterField, gbc);
}
}
}
因此,随着luck 和charisma 字段的更改,我们更新模型,模型实习生更新barter 值,这会触发PropertyChangeEvent,这允许我们更新易货字段
这似乎需要很多东西,但如果你能理解这些基本原则,那将需要很长的路要走。
看看:
有关我在 UI 代码中所做的一些事情的更多信息。我强烈建议您尽快停止使用表单编辑器并开始手动编写 UI。您将更深入地了解布局管理器如何工作以及如何协同工作、创建复杂的 UI,以及何时以及如何分离代码以降低复杂性并增加重用。这也将帮助您了解表单编辑器何时有用,何时无用;)