【问题标题】:Why is firePropertyChange(String propertyName, Object oldValue, Object newValue) protected and not public?为什么 firePropertyChange(String propertyName, Object oldValue, Object newValue) 受保护而不是公开的?
【发布时间】:2023-04-03 15:52:01
【问题描述】:

问题是我正在处理来自JCalendar 库的IDateEditor 接口实现,我注意到Component.firePropertyChange(String propertyName, Object oldValue, Object newValue) 方法不是公开的,而是受保护的。情况如下图所示:

public class DateFormattedTextField implements IDateEditor {

    private JFormattedTextField editor;        
    private DateUtil dateUtil;

    ...

    @Override
    public void setDate(Date date) {
        Date oldDate = (Date)editor.getValue();            
        if(dateUtil.checkDate(date)){
            editor.setValue(date);
            editor.firePropertyChange("date", oldDate, date); // <-- error here
        }
    }

}

如您所见,由于此方法受到保护,我无法触发属性更改。当然,如果我让我的类从 JFormattedTextfield 扩展而不是使用简单的变量,我可以轻松摆脱这个问题。

public class DateFormattedTextField extends JFormattedTextField implements IDateEditor {

    private DateUtil dateUtil;

    ...

    @Override
    public void setDate(Date date) {
        Date oldDate = (Date)getValue();            
        if(dateUtil.checkDate(date)){
            setValue(date);
            firePropertyChange("date", oldDate, date); // <-- No problem here
        }
    }
}

但这不是我要问的。我想知道:为什么要保护这个方法?

我知道这应该是一些设计问题,但我不知道为什么会这样,特别是考虑到大多数触发属性更改事件的方法都是公开的:

也许最有经验的开发人员可以对此有所了解。提前致谢。

附录:

到目前为止,这是我的代码。随意使用/修改/玩它。

public class DateFormattedTextField implements IDateEditor {

    private JFormattedTextField editor;        
    private DateUtil dateUtil;
    private DateFormat dateFormat;
    private String dateFormatString;

    public DateFormattedTextField(){
        dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
        editor = new JFormattedTextField(dateFormat);
        editor.setColumns(10);
        editor.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
        dateUtil = new DateUtil();            
    }

    @Override
    public Date getDate() {
        return (Date)editor.getValue();
    }

    @Override
    public void setDate(Date date) {
        Date oldDate = (Date)editor.getValue();            
        if(dateUtil.checkDate(date)){
            editor.setValue(date);
            editor.firePropertyChange("date", oldDate, date); // <-- error here
        }
    }

    @Override
    public void setDateFormatString(String dateFormatString) {
        this.dateFormatString = dateFormatString;
    }

    @Override
    public String getDateFormatString() {
        return this.dateFormatString;
    }

    @Override
    public void setSelectableDateRange(Date min, Date max) {
        dateUtil.setSelectableDateRange(min, max);
    }

    @Override
    public Date getMaxSelectableDate() {
        return dateUtil.getMaxSelectableDate();
    }

    @Override
    public Date getMinSelectableDate() {
        return dateUtil.getMinSelectableDate();
    }

    @Override
    public void setMaxSelectableDate(Date max) {
        dateUtil.setMaxSelectableDate(max);
    }

    @Override
    public void setMinSelectableDate(Date min) {
        dateUtil.setMinSelectableDate(min);
    }

    @Override
    public JComponent getUiComponent() {
        return editor;
    }

    @Override
    public void setLocale(Locale locale) {
        editor.setLocale(locale); // to be reviewed
    }

    @Override
    public void setEnabled(boolean enabled) {
        editor.setEnabled(enabled);
    }

    @Override
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        editor.addPropertyChangeListener(listener);
    }

    @Override
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        editor.addPropertyChangeListener(propertyName, listener);
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        editor.removePropertyChangeListener(listener);
    }

    @Override
    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        editor.removePropertyChangeListener(propertyName, listener);
    }
}

【问题讨论】:

  • 因为更改通知是编辑的固有责任 - 其他人不应干涉
  • @kleopatra 你需要回答这个问题!
  • 您的实现中存在一个小错误:向某个对象添加侦听器的客户端期望 that 对象作为事件源,因此到某个委托的干净路由意味着拦截来自委托的任何通知并在将源设置为自身后重新触发
  • @kleopatra 感谢您指出这一点,我会记住这一点 :)

标签: java swing event-handling jcalendar


【解决方案1】:

更改通知是任何可观察对象(通常,尤其是 java bean)的固有责任:如果在其绑定的任何属性发生更改时不触发 PropertyChangeEvent,它将违反其合同。在这种情况下,任何其他方都不需要使用 fireXX 方法。

因此,拥有比受保护范围更广的范围根本没有任何意义。如果你觉得需要它,那你就做错了。

【讨论】:

  • 您好!感谢您的回答:) 这对我来说完全有意义,但它让我不由自主地问为什么有公共方法来触发属性更改事件呢?例如:public void firePropertyChange(String propertyName, double oldValue, double newValue)。这些方法是从 AWT 继承的,之前是 Swing 继承的吗?这些存在是为了向后兼容吗?我认为我不需要公开这个方法,因为我已经说过可以简单地从 JFormattedTextField 扩展我的类并解决问题。实际上这样做会完全符合我认为的答案。
  • 设计意外?我的猜测和你的一样好 :-) 顺便说一句,你为什么要扩展 JFormattedTextField?通常,JXX 用于按原样使用,因此您在扩展时处于错误轨道的可能性非零......
【解决方案2】:

为了完整起见,我添加了这个答案。遵循@kleopatra 的明智解释和 cmets,我意识到我正在混合概念和责任。在这种情况下,属性更改通知不是用作底层编辑器组件的JFormattedTextField 的责任,而是IDateEditor 实现本身的责任。

因此,为了实现接口,我使用了PropertyChangeSupport 来保存属性更改侦听器的列表并通过PropertyChangeEvent 通知它们:

public class DefaultDateEditor implements IDateEditor {
    ...
    private final JFormattedTextField editor;
    private final PropertyChangeSupport propertyChangeSupport;
    ...

    public DefaultDateEditor() {
        ...
        propertyChangeSupport = new PropertyChangeSupport(this);
        ...
        editor = new JFormattedTextField();
        ...
    }

    @Override
    public final void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    @Override
    public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
    }

    @Override
    public final void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    @Override
    public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
    }

    public final PropertyChangeListener[] getPropertyChangeListeners() {
        return propertyChangeSupport.getPropertyChangeListeners();
    }

    public final PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
        return propertyChangeSupport.getPropertyChangeListeners(propertyName);
    }

    protected final void firePropertyChangeEvent(String propertyName, Object oldValue, Object newValue) {
        propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }
    ...
}

如您所见,添加/删除PropertyChangeListeners 并在属性更改事件上通知它们的任务委托给PropertyChangeSupport 类成员,但事件的来源是this,即IDateEditor接口实现者。正如@kleopatra 所说,这是客户期望成为事件源的源,而不是底层编辑器组件。

下面是生成的类的完整代码(对不起扩展)。如果您愿意,请随意使用/修改或玩耍。

/**
 * Custom implementation of {@code IDateEditor} interface. Unlike the default 
 * implementation provided with JCalendar library, this won't allow invalid 
 * inputs of any kind from the user.
 * 
 * @author dic19
 */
public class DefaultDateEditor implements IDateEditor {

    private Date date;
    private String dateFormatString;
    private Locale locale;
    private SimpleDateFormat dateFormat;
    private final DateFormatter dateFormatter;
    private final JFormattedTextField editor;
    private final DateUtil dateUtil;
    private final PropertyChangeSupport propertyChangeSupport;

    public DefaultDateEditor() {
        date = new Date();
        dateUtil = new DateUtil();
        propertyChangeSupport = new PropertyChangeSupport(this);
        addPropertyChangeListener("dateFormatString", new DateFormatStringChangeListener());
        addPropertyChangeListener("locale", new LocaleChangeListener());

        dateFormatString = "yyyy-MM-dd HH:mm:ss";
        locale = Locale.getDefault();
        dateFormat = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
        dateFormat.applyPattern(dateFormatString);

        dateFormatter = new DateFormatter();
        dateFormatter.setCommitsOnValidEdit(true);
        dateFormatter.setAllowsInvalid(false);
        dateFormatter.setOverwriteMode(true);

        editor = new JFormattedTextField();
        editor.setValue(date);
        editor.setColumns(15);
        editor.setToolTipText(dateFormatString);
        editor.addPropertyChangeListener("value", new EditorValueChangeListener());

        installFormatterFactoryOnEditor();
    }

    private void installFormatterFactoryOnEditor() {
        dateFormatter.setFormat(dateFormat);
        DefaultFormatterFactory factory = editor.getFormatterFactory() instanceof DefaultFormatterFactory
                                        ? (DefaultFormatterFactory) editor.getFormatterFactory()
                                        : new DefaultFormatterFactory();
        factory.setDefaultFormatter(dateFormatter);
        factory.setDisplayFormatter(dateFormatter);
        factory.setEditFormatter(dateFormatter);
        factory.setNullFormatter(dateFormatter);
        editor.setFormatterFactory(factory);
    }

    @Override
    public Date getDate() {
        return date != null ? new Date(date.getTime()) : null;
    }

    @Override
    public void setDate(Date date) {
        if (dateUtil.checkDate(date)) {
            Date oldValue = this.date;
            this.date = date != null ? new Date(date.getTime()) : null;
            editor.setValue(this.date);
            firePropertyChangeEvent("date", oldValue, date);
        }
    }

    @Override
    public void setDateFormatString(String dateFormatString) {
        String oldDateFormat = this.dateFormatString;
        this.dateFormatString = dateFormatString;
        firePropertyChangeEvent("dateFormatString", oldDateFormat, dateFormatString);
    }

    @Override
    public String getDateFormatString() {
        return dateFormatString;
    }

    @Override
    public void setSelectableDateRange(Date min, Date max) {
        dateUtil.setSelectableDateRange(min, max);
    }

    @Override
    public Date getMaxSelectableDate() {
        return dateUtil.getMaxSelectableDate();
    }

    @Override
    public Date getMinSelectableDate() {
        return dateUtil.getMinSelectableDate();
    }

    @Override
    public void setMaxSelectableDate(Date max) {
        dateUtil.setMaxSelectableDate(max);
    }

    @Override
    public void setMinSelectableDate(Date min) {
        dateUtil.setMinSelectableDate(min);
    }

    @Override
    public JComponent getUiComponent() {
        return editor;
    }

    @Override
    public void setLocale(Locale locale) {
        Locale oldLocale = this.locale;
        this.locale = locale;
        firePropertyChangeEvent("locale", oldLocale, locale);
    }

    @Override
    public void setEnabled(boolean enabled) {
        editor.setEnabled(enabled);
    }

    @Override
    public final void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    @Override
    public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
    }

    @Override
    public final void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    @Override
    public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
    }

    public final PropertyChangeListener[] getPropertyChangeListeners() {
        return propertyChangeSupport.getPropertyChangeListeners();
    }

    public final PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
        return propertyChangeSupport.getPropertyChangeListeners(propertyName);
    }

    protected final void firePropertyChangeEvent(String propertyName, Object oldValue, Object newValue) {
        propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }

    private class EditorValueChangeListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("value".equals(evt.getPropertyName())) {
                System.out.println("Old value:" + evt.getOldValue());
                System.out.println("New value:" + evt.getNewValue());
                setDate((Date)evt.getNewValue());
            }
        }
    }

    private class DateFormatStringChangeListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("dateFormatString".equals(evt.getPropertyName())) {
                dateFormat.applyPattern(dateFormatString);
                editor.setToolTipText(dateFormatString);
                installFormatterFactoryOnEditor();                    
            }
        }
    }

    private class LocaleChangeListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("locale".equals(evt.getPropertyName())) {
                dateFormat = (SimpleDateFormat)DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
                dateFormat.applyPattern(dateFormatString);
                editor.setLocale(locale);
                installFormatterFactoryOnEditor();
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 2018-10-29
    • 1970-01-01
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    • 2021-12-31
    相关资源
    最近更新 更多