根据用户选择更改 JLabel
Change JLabel based on what user choose
我的任务是做一个简单的订单系统。我想要 JLabel
(金额:0.00 美元)显示用户为他的汉堡和调味品选择的相应金额。例如,如果用户点击牛肉,标签将变为“金额:4.00 美元”,当他选择一种调味品时,它会根据他选择的调味品数量在总额中增加 0.50 美元,反之亦然。此外,当用户取消选中调味品 (JCheckBox
) 时,它会从总金额中扣除 $0.50 美元。
我的牛肉码JRadioButton
:
private void beef_radioBtnActionPerformed(java.awt.event.ActionEvent evt) {
total_amount.setText("Amount: .00");
ketchup_Checkbox.setEnabled(true);
mustard_Checkbox.setEnabled(true);
pickles_Checkbox.setEnabled(true);
}
番茄酱代码 JCheckBox
:
private void ketchup_CheckboxActionPerformed(java.awt.event.ActionEvent evt) {
float condiments_amount = (float) 0.50;
float beef_amount = (float) 4.00;
float total;
if (beef_radioBtn.isSelected()){
total = beef_amount + condiments_amount;
total_amount.setText("Amount: $" + decimal.format(total));
if (!ketchup_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 - condiments_amount));
}
else if (mustard_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 + condiments_amount));
}
else if (pickles_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 + condiments_amount));
}
}
}
好的,系好安全带,这将是一段旅程。
您可以使用的最强大的概念之一是“模型”的概念。模型只是对某些东西进行“建模”的东西,并且是一种分隔程序不同区域的方法。因此,模型对数据建模(将其视为容器),然后视图将使用这些模型将数据格式化给用户(关注点分离)。模型还可能包含业务逻辑或根据其要求执行计算。
这使您可以集中概念,这样您就不会重复自己或忘记做事。这也是一种改变程序部分工作方式的方法,也称为“委托”
起点,一些接口
嗯,说了很多“废话”,让我们开始吧。我更喜欢用interface
来描述事物,它提供了很大的自由度,因为你可以将不同的interface
放在一起以适应不同的需求。
菜单
好的,简单的概念,这将是一个可供出售的物品列表
public interface Menu {
public List<MainMenuItem> getMenuItems();
}
菜单项
菜单项的描述,非常基础
public interface MenuItem {
public String getDescription();
public double getPrice();
}
“主要”菜单项
这些都是“顶级”、“独立”菜单项,在我们的例子中,可以有调味品:D
public interface MainMenuItem extends MenuItem {
public List<Condiment> getCondiments();
}
调味品
调味品是“特殊的”MenuItem
,因为它们与 MainMenuItem
public interface Condiment extends MenuItem {
}
汉堡
这只是您可以做的一些事情的演示,Burger
没有什么特别的,但是正如您将看到的,我们可以使用这个概念来做不同的事情
public interface Burger extends MainMenuItem {
}
订单
最后,“订单”,我们点了什么以及我们想要什么调味品
public interface Order {
public MainMenuItem getItem();
public void setItem(MainMenuItem item);
public List<Condiment> getCondiments();
public void addCondiment(Condiment condiment);
public void removeCondiment(Condiment condiment);
public double getTally();
}
这很好地展示了模型的力量。 Order
有一个getTally
方法,用来计算欠的。该模型的不同实现可能会应用不同的计算,例如税收或折扣
实施
好的,既然你可能已经知道,我们无法创建 interface
的实例,我们需要一些“默认”实现才能使用...
public class DefaultOrder implements Order {
private MainMenuItem item;
private List<Condiment> condiments = new ArrayList<>();
@Override
public MainMenuItem getItem() {
return item;
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
@Override
public double getTally() {
double tally = 0;
if (item != null) {
tally += item.getPrice();
}
for (Condiment condiment : condiments) {
tally += condiment.getPrice();
}
return tally;
}
@Override
public void setItem(MainMenuItem item) {
this.item = item;
// Oh look, we've established a "rule" that this model
// applies, by itself, sweet
condiments.clear();
}
@Override
public void addCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
// Probably should check for duplicates
condiments.add(condiment);
}
@Override
public void removeCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
condiments.remove(condiment);
}
}
public class DefaultMenu implements Menu {
private List<MainMenuItem> menuItems = new ArrayList<>();
public void add(MainMenuItem menuItem) {
menuItems.add(menuItem);
}
@Override
public List<MainMenuItem> getMenuItems() {
return Collections.unmodifiableList(menuItems);
}
}
public abstract class AbstractMenuItem implements MenuItem {
private String description;
private double price;
public AbstractMenuItem(String description, double price) {
this.description = description;
this.price = price;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
}
public class DefaultCondiment extends AbstractMenuItem implements Condiment {
public DefaultCondiment(String description, double price) {
super(description, price);
}
}
public class DefaultBurger extends AbstractMenuItem implements Burger {
private List<Condiment> condiments;
public DefaultBurger(String description, double price, List<Condiment> condiments) {
super(description, price);
// Protect ourselves from external modifications
this.condiments = new ArrayList<>(condiments);
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
}
好吧,不要太纠结于此,但请看一下 abstract
的用法。 AbstractMenuItem
封装了所有 MenuItem
实现都需要的许多“通用”功能,所以我们不需要重复自己,亲爱的。
其中一些实现已经在制定决策或应用规则。例如,DefaultOrder
将在 MainMenuItem
更改时清除 condiments
。它还可以确保所应用的调味品实际上可用于该项目。
另请注意,tally
方法不是存储的 属性,而是在您每次调用它时重新计算。这是设计决定,将其改为存储 属性 并不难,因此每次更改 MenuMenuItem
、添加 and/or 删除调味品时, 属性已更新,但我感到懒惰。但是你可以看到这些东西是如何改变的,它会影响这些模型的所有用户,亲爱的:D
好的,但这实际上是如何回答问题的?好吧,实际上相当多。
所以,我们的想法是,您从空白开始 Order
。用户选择一个“主要项目”(即汉堡),将其设置为 Order
然后更新 UI 作为响应。 UI 要求 Order
计算 tally
并将其呈现给用户。
此外,同样的概念也适用于调味品。每次用户添加或删除调味品时,Order
都会更新,而您会更新 UI.
好的,但也许,通过示例更容易理解...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
List<Condiment> condiments = new ArrayList<>(3);
condiments.add(new DefaultCondiment("Ketchup", 0.5));
condiments.add(new DefaultCondiment("Mustard", 0.5));
condiments.add(new DefaultCondiment("Pickles", 0.5));
DefaultMenu menu = new DefaultMenu();
menu.add(new DefaultBurger("Beef", 4.0, condiments));
menu.add(new DefaultBurger("Chicken", 3.5, condiments));
menu.add(new DefaultBurger("Veggie", 4.0, condiments));
MenuPane menuPane = new MenuPane();
menuPane.setMenu(menu);
frame.add(menuPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MenuPane extends JPanel {
private Menu menu;
private Order order;
private List<Condiment> selectedCondiments = new ArrayList<>();
private JPanel burgerPanel;
private JPanel condimentPanel;
private JPanel totalPanel;
private JLabel totalLabel;
private JButton clearButton;
private JButton payButton;
private NumberFormat currencyFormatter;
public MenuPane() {
setLayout(new GridBagLayout());
order = new DefaultOrder();
burgerPanel = new JPanel();
burgerPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
condimentPanel = new JPanel();
condimentPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
totalPanel = makeTotalPanel();
totalPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 0.5;
add(burgerPanel, gbc);
gbc.gridy++;
add(condimentPanel, gbc);
gbc.gridy++;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(totalPanel, gbc);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
protected NumberFormat getCurrentFormatter() {
if (currencyFormatter != null) {
return currencyFormatter;
}
currencyFormatter = NumberFormat.getCurrencyInstance();
currencyFormatter.setMinimumFractionDigits(2);
return currencyFormatter;
}
protected JPanel makeTotalPanel() {
JPanel totalPanel = new JPanel(new GridBagLayout());
totalLabel = new JLabel();
clearButton = new JButton("CLR");
payButton = new JButton("PAY");
clearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
order = new DefaultOrder();
buildCondiments();
orderDidChange();
}
});
GridBagConstraints gbc = new GridBagConstraints();
gbc.weightx = 1;
gbc.gridx = 0;
gbc.gridy = 0;
totalPanel.add(totalLabel, gbc);
gbc.weightx = 0;
gbc.gridx++;
totalPanel.add(clearButton, gbc);
gbc.gridx++;
totalPanel.add(payButton, gbc);
return totalPanel;
}
protected void buildBurgerMenu() {
burgerPanel.removeAll();
burgerPanel.setLayout(new GridBagLayout());
if (menu == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
ButtonGroup bg = new ButtonGroup();
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JComponent)) {
return;
}
JComponent comp = (JComponent) e.getSource();
Object obj = comp.getClientProperty("MenuItem");
// I'm putting this here to demonstrate part of the concept
// of polymorphism - techncially, we don't have to care
// of it's a burger or some other type of menu item,
// only that this is going to represent the "main" item
if (!(obj instanceof MainMenuItem)) {
return;
}
MainMenuItem item = (MainMenuItem) obj;
order.setItem(item);
buildCondiments();
orderDidChange();
}
};
System.out.println(menu.getMenuItems().size());
for (MenuItem item : menu.getMenuItems()) {
// Only interested in burgers
// Could have the Menu do this, but that's a design
// decision
if (!(item instanceof Burger)) {
continue;
}
Burger burger = (Burger) item;
JRadioButton btn = new JRadioButton(burger.getDescription() + " (" + getCurrentFormatter().format(burger.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("MenuItem", burger);
bg.add(btn);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
burgerPanel.add(btn, gbc);
}
}
protected void buildCondiments() {
condimentPanel.removeAll();
condimentPanel.setLayout(new GridBagLayout());
if (menu == null || order.getItem() == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JCheckBox)) {
return;
}
JCheckBox checkBox = (JCheckBox) e.getSource();
Object obj = checkBox.getClientProperty("Condiment");
if (!(obj instanceof Condiment)) {
return;
}
Condiment condiment = (Condiment) obj;
if (checkBox.isSelected()) {
order.addCondiment(condiment);
} else {
order.removeCondiment(condiment);
}
orderDidChange();
}
};
for (Condiment condiment : order.getItem().getCondiments()) {
JCheckBox btn = new JCheckBox(condiment.getDescription() + " (" + getCurrentFormatter().format(condiment.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("Condiment", condiment);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
condimentPanel.add(btn, gbc);
}
}
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
order = new DefaultOrder();
buildBurgerMenu();
orderDidChange();
}
protected void orderDidChange() {
if (order == null) {
totalLabel.setText("Amount: " + getCurrentFormatter().format(0));
return;
}
// And now, some magic, how easy is it to get the expected
// tally amount!!
totalLabel.setText("Amount: " + getCurrentFormatter().format(order.getTally()));
}
}
public interface Menu {
public List<MainMenuItem> getMenuItems();
}
public interface MenuItem {
public String getDescription();
public double getPrice();
}
public interface Condiment extends MenuItem {
}
public interface MainMenuItem extends MenuItem {
public List<Condiment> getCondiments();
}
public interface Burger extends MainMenuItem {
}
public interface Order {
public MainMenuItem getItem();
public void setItem(MainMenuItem item);
public List<Condiment> getCondiments();
public void addCondiment(Condiment condiment);
public void removeCondiment(Condiment condiment);
public double getTally();
}
public class DefaultOrder implements Order {
private MainMenuItem item;
private List<Condiment> condiments = new ArrayList<>();
@Override
public MainMenuItem getItem() {
return item;
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
@Override
public double getTally() {
double tally = 0;
if (item != null) {
tally += item.getPrice();
}
for (Condiment condiment : condiments) {
tally += condiment.getPrice();
}
return tally;
}
@Override
public void setItem(MainMenuItem item) {
this.item = item;
// Oh look, we've established a "rule" that this model
// applies, by itself, sweet
condiments.clear();
}
@Override
public void addCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
// Probably should check for duplicates
condiments.add(condiment);
}
@Override
public void removeCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
condiments.remove(condiment);
}
}
public class DefaultMenu implements Menu {
private List<MainMenuItem> menuItems = new ArrayList<>();
public void add(MainMenuItem menuItem) {
menuItems.add(menuItem);
}
@Override
public List<MainMenuItem> getMenuItems() {
return Collections.unmodifiableList(menuItems);
}
}
public abstract class AbstractMenuItem implements MenuItem {
private String description;
private double price;
public AbstractMenuItem(String description, double price) {
this.description = description;
this.price = price;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
}
public class DefaultCondiment extends AbstractMenuItem implements Condiment {
public DefaultCondiment(String description, double price) {
super(description, price);
}
}
public class DefaultBurger extends AbstractMenuItem implements Burger {
private List<Condiment> condiments;
public DefaultBurger(String description, double price, List<Condiment> condiments) {
super(description, price);
// Protect ourselves from external modifications
this.condiments = new ArrayList<>(condiments);
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
}
}
好吧,要理解的东西太多了。让我们仔细看看 buildBurgerMenu
方法。每当主菜单更改时都会调用此方法。
注意这个方法中用到的actionListener
,只有一个,所有按钮共用
protected void buildBurgerMenu() {
burgerPanel.removeAll();
burgerPanel.setLayout(new GridBagLayout());
if (menu == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
ButtonGroup bg = new ButtonGroup();
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JComponent)) {
return;
}
JComponent comp = (JComponent) e.getSource();
Object obj = comp.getClientProperty("MenuItem");
// I'm putting this here to demonstrate part of the concept
// of polymorphism - techncially, we don't have to care
// of it's a burger or some other type of menu item,
// only that this is going to represent the "main" item
if (!(obj instanceof MainMenuItem)) {
return;
}
MainMenuItem item = (MainMenuItem) obj;
order.setItem(item);
buildCondiments();
orderDidChange();
}
};
System.out.println(menu.getMenuItems().size());
for (MenuItem item : menu.getMenuItems()) {
// Only interested in burgers
// Could have the Menu do this, but that's a design
// decision
if (!(item instanceof Burger)) {
continue;
}
Burger burger = (Burger) item;
JRadioButton btn = new JRadioButton(burger.getDescription() + " (" + getCurrentFormatter().format(burger.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("MenuItem", burger);
bg.add(btn);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
burgerPanel.add(btn, gbc);
}
}
每当 actionListener
被触发时(例如通过用户交互),它都会做出一系列决定,如果一切顺利,最终会更新 Order
,已调用 buildCondiments
和 orderDidChange
方法,更新可用调味品并更新 UI 的计数。
这一切都是以“抽象”的方式完成的。 actionListener
不关心用户选择的 MainMenuItem
是什么类型,这不会改变它的工作流程,它只需要将项目应用到 Order
。同样,Order
并不关心,因为它只需要 price
信息来计算计数。
因此您可以添加新的菜单项 and/or 更改价格并且一切正常 ()。
让我们看看orderDidChange
方法...
protected void orderDidChange() {
if (order == null) {
totalLabel.setText("Amount: " + getCurrentFormatter().format(0));
return;
}
// And now, some magic, how easy is it to get the expected
// tally amount!!
totalLabel.setText("Amount: " + getCurrentFormatter().format(order.getTally()));
}
不是超级复杂吧!所有的工作都是由 MODEL!
完成的
我遗漏了什么
为简洁起见,我省略了另一个经常与模型一起使用的概念,即“观察者模式”的概念。
观察者模式允许感兴趣的一方在其他对象发生变化时得到通知。这是一个很常见的概念,你已经使用过,ActionListener
是观察者模式的一个例子。它允许您在给定对象触发时“观察”“动作事件”。
当然可以,但这有什么用?
好吧,现在想象一下,如果不是每次您想更新 UI 时都必须手动调用 orderDidChange
(或者甚至忘记并花几个小时调试原因),MenuPane
可以直接将自己注册为 Order
的观察者,并在顺序更改时收到通知!!超级甜!
这进一步帮助您解耦代码,并使 UI 以多种独立于模型或其他代码要求的真实方式变得超级容易。
型号
我的任务是做一个简单的订单系统。我想要 JLabel
(金额:0.00 美元)显示用户为他的汉堡和调味品选择的相应金额。例如,如果用户点击牛肉,标签将变为“金额:4.00 美元”,当他选择一种调味品时,它会根据他选择的调味品数量在总额中增加 0.50 美元,反之亦然。此外,当用户取消选中调味品 (JCheckBox
) 时,它会从总金额中扣除 $0.50 美元。
我的牛肉码JRadioButton
:
private void beef_radioBtnActionPerformed(java.awt.event.ActionEvent evt) {
total_amount.setText("Amount: .00");
ketchup_Checkbox.setEnabled(true);
mustard_Checkbox.setEnabled(true);
pickles_Checkbox.setEnabled(true);
}
番茄酱代码 JCheckBox
:
private void ketchup_CheckboxActionPerformed(java.awt.event.ActionEvent evt) {
float condiments_amount = (float) 0.50;
float beef_amount = (float) 4.00;
float total;
if (beef_radioBtn.isSelected()){
total = beef_amount + condiments_amount;
total_amount.setText("Amount: $" + decimal.format(total));
if (!ketchup_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 - condiments_amount));
}
else if (mustard_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 + condiments_amount));
}
else if (pickles_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 + condiments_amount));
}
}
}
好的,系好安全带,这将是一段旅程。
您可以使用的最强大的概念之一是“模型”的概念。模型只是对某些东西进行“建模”的东西,并且是一种分隔程序不同区域的方法。因此,模型对数据建模(将其视为容器),然后视图将使用这些模型将数据格式化给用户(关注点分离)。模型还可能包含业务逻辑或根据其要求执行计算。
这使您可以集中概念,这样您就不会重复自己或忘记做事。这也是一种改变程序部分工作方式的方法,也称为“委托”
起点,一些接口
嗯,说了很多“废话”,让我们开始吧。我更喜欢用interface
来描述事物,它提供了很大的自由度,因为你可以将不同的interface
放在一起以适应不同的需求。
菜单
好的,简单的概念,这将是一个可供出售的物品列表
public interface Menu {
public List<MainMenuItem> getMenuItems();
}
菜单项
菜单项的描述,非常基础
public interface MenuItem {
public String getDescription();
public double getPrice();
}
“主要”菜单项
这些都是“顶级”、“独立”菜单项,在我们的例子中,可以有调味品:D
public interface MainMenuItem extends MenuItem {
public List<Condiment> getCondiments();
}
调味品
调味品是“特殊的”MenuItem
,因为它们与 MainMenuItem
public interface Condiment extends MenuItem {
}
汉堡
这只是您可以做的一些事情的演示,Burger
没有什么特别的,但是正如您将看到的,我们可以使用这个概念来做不同的事情
public interface Burger extends MainMenuItem {
}
订单
最后,“订单”,我们点了什么以及我们想要什么调味品
public interface Order {
public MainMenuItem getItem();
public void setItem(MainMenuItem item);
public List<Condiment> getCondiments();
public void addCondiment(Condiment condiment);
public void removeCondiment(Condiment condiment);
public double getTally();
}
这很好地展示了模型的力量。 Order
有一个getTally
方法,用来计算欠的。该模型的不同实现可能会应用不同的计算,例如税收或折扣
实施
好的,既然你可能已经知道,我们无法创建 interface
的实例,我们需要一些“默认”实现才能使用...
public class DefaultOrder implements Order {
private MainMenuItem item;
private List<Condiment> condiments = new ArrayList<>();
@Override
public MainMenuItem getItem() {
return item;
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
@Override
public double getTally() {
double tally = 0;
if (item != null) {
tally += item.getPrice();
}
for (Condiment condiment : condiments) {
tally += condiment.getPrice();
}
return tally;
}
@Override
public void setItem(MainMenuItem item) {
this.item = item;
// Oh look, we've established a "rule" that this model
// applies, by itself, sweet
condiments.clear();
}
@Override
public void addCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
// Probably should check for duplicates
condiments.add(condiment);
}
@Override
public void removeCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
condiments.remove(condiment);
}
}
public class DefaultMenu implements Menu {
private List<MainMenuItem> menuItems = new ArrayList<>();
public void add(MainMenuItem menuItem) {
menuItems.add(menuItem);
}
@Override
public List<MainMenuItem> getMenuItems() {
return Collections.unmodifiableList(menuItems);
}
}
public abstract class AbstractMenuItem implements MenuItem {
private String description;
private double price;
public AbstractMenuItem(String description, double price) {
this.description = description;
this.price = price;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
}
public class DefaultCondiment extends AbstractMenuItem implements Condiment {
public DefaultCondiment(String description, double price) {
super(description, price);
}
}
public class DefaultBurger extends AbstractMenuItem implements Burger {
private List<Condiment> condiments;
public DefaultBurger(String description, double price, List<Condiment> condiments) {
super(description, price);
// Protect ourselves from external modifications
this.condiments = new ArrayList<>(condiments);
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
}
好吧,不要太纠结于此,但请看一下 abstract
的用法。 AbstractMenuItem
封装了所有 MenuItem
实现都需要的许多“通用”功能,所以我们不需要重复自己,亲爱的。
其中一些实现已经在制定决策或应用规则。例如,DefaultOrder
将在 MainMenuItem
更改时清除 condiments
。它还可以确保所应用的调味品实际上可用于该项目。
另请注意,tally
方法不是存储的 属性,而是在您每次调用它时重新计算。这是设计决定,将其改为存储 属性 并不难,因此每次更改 MenuMenuItem
、添加 and/or 删除调味品时, 属性已更新,但我感到懒惰。但是你可以看到这些东西是如何改变的,它会影响这些模型的所有用户,亲爱的:D
好的,但这实际上是如何回答问题的?好吧,实际上相当多。
所以,我们的想法是,您从空白开始 Order
。用户选择一个“主要项目”(即汉堡),将其设置为 Order
然后更新 UI 作为响应。 UI 要求 Order
计算 tally
并将其呈现给用户。
此外,同样的概念也适用于调味品。每次用户添加或删除调味品时,Order
都会更新,而您会更新 UI.
好的,但也许,通过示例更容易理解...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
List<Condiment> condiments = new ArrayList<>(3);
condiments.add(new DefaultCondiment("Ketchup", 0.5));
condiments.add(new DefaultCondiment("Mustard", 0.5));
condiments.add(new DefaultCondiment("Pickles", 0.5));
DefaultMenu menu = new DefaultMenu();
menu.add(new DefaultBurger("Beef", 4.0, condiments));
menu.add(new DefaultBurger("Chicken", 3.5, condiments));
menu.add(new DefaultBurger("Veggie", 4.0, condiments));
MenuPane menuPane = new MenuPane();
menuPane.setMenu(menu);
frame.add(menuPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MenuPane extends JPanel {
private Menu menu;
private Order order;
private List<Condiment> selectedCondiments = new ArrayList<>();
private JPanel burgerPanel;
private JPanel condimentPanel;
private JPanel totalPanel;
private JLabel totalLabel;
private JButton clearButton;
private JButton payButton;
private NumberFormat currencyFormatter;
public MenuPane() {
setLayout(new GridBagLayout());
order = new DefaultOrder();
burgerPanel = new JPanel();
burgerPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
condimentPanel = new JPanel();
condimentPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
totalPanel = makeTotalPanel();
totalPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 0.5;
add(burgerPanel, gbc);
gbc.gridy++;
add(condimentPanel, gbc);
gbc.gridy++;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(totalPanel, gbc);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
protected NumberFormat getCurrentFormatter() {
if (currencyFormatter != null) {
return currencyFormatter;
}
currencyFormatter = NumberFormat.getCurrencyInstance();
currencyFormatter.setMinimumFractionDigits(2);
return currencyFormatter;
}
protected JPanel makeTotalPanel() {
JPanel totalPanel = new JPanel(new GridBagLayout());
totalLabel = new JLabel();
clearButton = new JButton("CLR");
payButton = new JButton("PAY");
clearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
order = new DefaultOrder();
buildCondiments();
orderDidChange();
}
});
GridBagConstraints gbc = new GridBagConstraints();
gbc.weightx = 1;
gbc.gridx = 0;
gbc.gridy = 0;
totalPanel.add(totalLabel, gbc);
gbc.weightx = 0;
gbc.gridx++;
totalPanel.add(clearButton, gbc);
gbc.gridx++;
totalPanel.add(payButton, gbc);
return totalPanel;
}
protected void buildBurgerMenu() {
burgerPanel.removeAll();
burgerPanel.setLayout(new GridBagLayout());
if (menu == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
ButtonGroup bg = new ButtonGroup();
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JComponent)) {
return;
}
JComponent comp = (JComponent) e.getSource();
Object obj = comp.getClientProperty("MenuItem");
// I'm putting this here to demonstrate part of the concept
// of polymorphism - techncially, we don't have to care
// of it's a burger or some other type of menu item,
// only that this is going to represent the "main" item
if (!(obj instanceof MainMenuItem)) {
return;
}
MainMenuItem item = (MainMenuItem) obj;
order.setItem(item);
buildCondiments();
orderDidChange();
}
};
System.out.println(menu.getMenuItems().size());
for (MenuItem item : menu.getMenuItems()) {
// Only interested in burgers
// Could have the Menu do this, but that's a design
// decision
if (!(item instanceof Burger)) {
continue;
}
Burger burger = (Burger) item;
JRadioButton btn = new JRadioButton(burger.getDescription() + " (" + getCurrentFormatter().format(burger.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("MenuItem", burger);
bg.add(btn);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
burgerPanel.add(btn, gbc);
}
}
protected void buildCondiments() {
condimentPanel.removeAll();
condimentPanel.setLayout(new GridBagLayout());
if (menu == null || order.getItem() == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JCheckBox)) {
return;
}
JCheckBox checkBox = (JCheckBox) e.getSource();
Object obj = checkBox.getClientProperty("Condiment");
if (!(obj instanceof Condiment)) {
return;
}
Condiment condiment = (Condiment) obj;
if (checkBox.isSelected()) {
order.addCondiment(condiment);
} else {
order.removeCondiment(condiment);
}
orderDidChange();
}
};
for (Condiment condiment : order.getItem().getCondiments()) {
JCheckBox btn = new JCheckBox(condiment.getDescription() + " (" + getCurrentFormatter().format(condiment.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("Condiment", condiment);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
condimentPanel.add(btn, gbc);
}
}
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
order = new DefaultOrder();
buildBurgerMenu();
orderDidChange();
}
protected void orderDidChange() {
if (order == null) {
totalLabel.setText("Amount: " + getCurrentFormatter().format(0));
return;
}
// And now, some magic, how easy is it to get the expected
// tally amount!!
totalLabel.setText("Amount: " + getCurrentFormatter().format(order.getTally()));
}
}
public interface Menu {
public List<MainMenuItem> getMenuItems();
}
public interface MenuItem {
public String getDescription();
public double getPrice();
}
public interface Condiment extends MenuItem {
}
public interface MainMenuItem extends MenuItem {
public List<Condiment> getCondiments();
}
public interface Burger extends MainMenuItem {
}
public interface Order {
public MainMenuItem getItem();
public void setItem(MainMenuItem item);
public List<Condiment> getCondiments();
public void addCondiment(Condiment condiment);
public void removeCondiment(Condiment condiment);
public double getTally();
}
public class DefaultOrder implements Order {
private MainMenuItem item;
private List<Condiment> condiments = new ArrayList<>();
@Override
public MainMenuItem getItem() {
return item;
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
@Override
public double getTally() {
double tally = 0;
if (item != null) {
tally += item.getPrice();
}
for (Condiment condiment : condiments) {
tally += condiment.getPrice();
}
return tally;
}
@Override
public void setItem(MainMenuItem item) {
this.item = item;
// Oh look, we've established a "rule" that this model
// applies, by itself, sweet
condiments.clear();
}
@Override
public void addCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
// Probably should check for duplicates
condiments.add(condiment);
}
@Override
public void removeCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
condiments.remove(condiment);
}
}
public class DefaultMenu implements Menu {
private List<MainMenuItem> menuItems = new ArrayList<>();
public void add(MainMenuItem menuItem) {
menuItems.add(menuItem);
}
@Override
public List<MainMenuItem> getMenuItems() {
return Collections.unmodifiableList(menuItems);
}
}
public abstract class AbstractMenuItem implements MenuItem {
private String description;
private double price;
public AbstractMenuItem(String description, double price) {
this.description = description;
this.price = price;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
}
public class DefaultCondiment extends AbstractMenuItem implements Condiment {
public DefaultCondiment(String description, double price) {
super(description, price);
}
}
public class DefaultBurger extends AbstractMenuItem implements Burger {
private List<Condiment> condiments;
public DefaultBurger(String description, double price, List<Condiment> condiments) {
super(description, price);
// Protect ourselves from external modifications
this.condiments = new ArrayList<>(condiments);
}
@Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
}
}
好吧,要理解的东西太多了。让我们仔细看看 buildBurgerMenu
方法。每当主菜单更改时都会调用此方法。
注意这个方法中用到的actionListener
,只有一个,所有按钮共用
protected void buildBurgerMenu() {
burgerPanel.removeAll();
burgerPanel.setLayout(new GridBagLayout());
if (menu == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
ButtonGroup bg = new ButtonGroup();
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JComponent)) {
return;
}
JComponent comp = (JComponent) e.getSource();
Object obj = comp.getClientProperty("MenuItem");
// I'm putting this here to demonstrate part of the concept
// of polymorphism - techncially, we don't have to care
// of it's a burger or some other type of menu item,
// only that this is going to represent the "main" item
if (!(obj instanceof MainMenuItem)) {
return;
}
MainMenuItem item = (MainMenuItem) obj;
order.setItem(item);
buildCondiments();
orderDidChange();
}
};
System.out.println(menu.getMenuItems().size());
for (MenuItem item : menu.getMenuItems()) {
// Only interested in burgers
// Could have the Menu do this, but that's a design
// decision
if (!(item instanceof Burger)) {
continue;
}
Burger burger = (Burger) item;
JRadioButton btn = new JRadioButton(burger.getDescription() + " (" + getCurrentFormatter().format(burger.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("MenuItem", burger);
bg.add(btn);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
burgerPanel.add(btn, gbc);
}
}
每当 actionListener
被触发时(例如通过用户交互),它都会做出一系列决定,如果一切顺利,最终会更新 Order
,已调用 buildCondiments
和 orderDidChange
方法,更新可用调味品并更新 UI 的计数。
这一切都是以“抽象”的方式完成的。 actionListener
不关心用户选择的 MainMenuItem
是什么类型,这不会改变它的工作流程,它只需要将项目应用到 Order
。同样,Order
并不关心,因为它只需要 price
信息来计算计数。
因此您可以添加新的菜单项 and/or 更改价格并且一切正常 ()。
让我们看看orderDidChange
方法...
protected void orderDidChange() {
if (order == null) {
totalLabel.setText("Amount: " + getCurrentFormatter().format(0));
return;
}
// And now, some magic, how easy is it to get the expected
// tally amount!!
totalLabel.setText("Amount: " + getCurrentFormatter().format(order.getTally()));
}
不是超级复杂吧!所有的工作都是由 MODEL!
完成的我遗漏了什么
为简洁起见,我省略了另一个经常与模型一起使用的概念,即“观察者模式”的概念。
观察者模式允许感兴趣的一方在其他对象发生变化时得到通知。这是一个很常见的概念,你已经使用过,ActionListener
是观察者模式的一个例子。它允许您在给定对象触发时“观察”“动作事件”。
当然可以,但这有什么用?
好吧,现在想象一下,如果不是每次您想更新 UI 时都必须手动调用 orderDidChange
(或者甚至忘记并花几个小时调试原因),MenuPane
可以直接将自己注册为 Order
的观察者,并在顺序更改时收到通知!!超级甜!
这进一步帮助您解耦代码,并使 UI 以多种独立于模型或其他代码要求的真实方式变得超级容易。
型号