Groovy: 比JavaBean好用的GroovyBean - Bindable和Vetoable
在
前面的文章中我们看到了GroovyBean的基本语法。除了能用少量的代码实现JavaBean的功能以外,GroovyBean还提供了JavaBean标准里的
绑定属性和限制属性。绑定属性是一个属性,当它的值发生变化是会通知其他的Bean。限制属性是一个属性,当这个属性变化会被其他Bean验证。在这里的如果有需要其他的Bean可以阻止变化。
在Groovy里实现这两个属性是非常简单的。通过@Bindable和@Vetoable标注就可以轻松实现。下面是一个例子:
import groovy.beans.*
class Car {
int numberOfDoors
@Vetoable String model
@Vetoable String brand
boolean automatic
@Bindable double price
String toString() {
"[Car details => brand: '${brand}', model: '${model}', #doors: '${numberOfDoors}', automatic: '${automatic}', price: '${price}']"
}
}
这就OK了。当我们编译上面的类的时候Groovy会自动加上所有需要的addXXXListener方法。上面的类如果用Java来写是这样的:
import java.beans.*;
public class Car {
private int numberOfDoors;
private String model;
private String brand;
private boolean automatic;
private double price;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private final VetoableChangeSupport vcs = new VetoableChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public void addVetoableChangeListener(VetoableChangeListener listener) {
vcs.addVetoableChangeListener(listener);
}
public void removeVetoableChangeListener(VetoableChangeListener listener) {
vcs.removeVetoableChangeListener(listener);
}
public void setPrice(double price) {
double oldPrice = this.price;
this.price = price;
pcs.firePropertyChange("price", oldPrice, price);
}
public double getPrice() {
return this.price;
}
public void setModel(String model) throws PropertyVetoException {
String oldModel = this.model;
vcs.fireVetoableChange("model", oldModel, model);
this.model = model;
pcs.firePropertyChange("model", oldModel, model);
}
public String getModel() {
return this.model;
}
public void setBrand(String model) throws PropertyVetoException {
String oldBrand = this.brand;
vcs.fireVetoableChange("model", oldBrand, brand);
this.brand = brand;
pcs.firePropertyChange("model", oldBrand, brand);
}
public String getBrand() {
return this.brand;
}
public void setNumberOfDoors(int numberOfDoors) {
this.numberOfDoors = numberOfDoors;
}
public int getNumberOfDoors() {
return numberOfDoors;
}
public void setAutomatic(boolean automatic) {
this.automatic = automatic;
}
public boolean isAutomatic() {
return this.automatic;
}
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("[Car details => brand: '");
builder.append(brand);
builder.append("', model: '");
builder.append(model);
builder.append("', #doors: '");
builder.append(numberOfDoors);
builder.append("', automatic: '");
builder.append(automatic);
builder.append("', price: '");
builder.append(price);
builder.append("']");
return builder.toString();
}
}
通过上面的代码,好处显而易见了
下面我们用一段Groovy脚本看一下怎么监听propertychange和vetoablechange事件。同样用Groovy来实现的话会简单的多。在Groovy中我们可以用闭包来实现Listener接口。下面的代码监听属性变化并阻止值发生变化:
import groovy.beans.*
import java.beans.*
def toyota = new Car(brand: 'Toyota', model: 'Verso', price: 28919, numberOfDoors: 5)
toyota.propertyChange = {
if (it.propertyName == 'price') {
println "The price has changed. Inform sales the new price is '${it.newValue}'."
}
}
toyota.vetoableChange = { PropertyChangeEvent pce ->
if (pce.propertyName == "brand") {
if (!(pce.newValue in ['Toyota', 'Lexus'])) {
throw new PropertyVetoException('New value is not Toyota or Lexus', pce)
}
}
if (pce.propertyName == "model") {
if (pce.newValue ==~ /.*\d+.*/) {
throw new PropertyVetoException('No numbers in model names allowed.', pce)
}
}
}
toyota.price = 30995
assert 30995 == toyota.price
toyota.brand = 'Lexus'
assert 'Lexus' == toyota.brand
try {
toyota.brand = 'AUDI'
assert false: 'We should not be able to set this value.'
} catch (PropertyVetoException e) {
assert true
}
try {
toyota.model = 'A5'
assert false: 'We should not be able to set this value.'
} catch (PropertyVetoException e) {
assert true
}
Groovy的Car类是被编译成Java的字节码,所以我们可以在普通的Java程序中使用上面的Car类。下面是在Java中使用Car的监听器接口。注意我们必须用groovyc来编译上面的代码,并且不能使用匿名内部类最后接口实现。
import java.beans.*;
import java.util.regex.*;
public class CarApp implements PropertyChangeListener, VetoableChangeListener {
public static void main(String[] args) {
Car toyota = new Car();
toyota.setModel("Verso");
toyota.setBrand("Toyota");
toyota.setNumberOfDoors(5);
toyota.setPrice(28919);
CarApp app = new CarApp();
toyota.addPropertyChangeListener(app);
toyota.addVetoableChangeListener(app);
toyota.setPrice(30995);
toyota.setBrand("Lexus");
try {
toyota.setBrand("AUDI");
} catch (PropertyVetoException e) {
System.out.println("Brand is not changed.");
}
try {
toyota.setModel("A5");
} catch (PropertyVetoException e) {
System.out.println("Model is not changed.");
}
}
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("price")) {
System.out.println("The price has changed. Inform sales the new price is '" + evt.getNewValue() + "'.");
}
}
public void vetoableChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("brand")) {
if (!isValidBrand(evt.getNewValue())) {
throw new PropertyVetoException("New value is not Toyota or Lexus", evt)
}
}
if (evt.getPropertyName().equals("model")) {
if (!isValidModel(evt.getNewValue())) {
throw new PropertyVetoException("No numbers in model names allowed.", evt)
}
}
}
private boolean isValidBrand(String newValue) {
final String[] names = new String[2];
names[0] = "Toyota";
names[1] = "Lexus";
for (String name: names) {
if (newValue.equals(name)) {
return true;
}
}
return false;
}
private boolean isValidModel(String model) {
return !Pattern.matches(".*\\d+.*", model);
}
}
怎么样?使用@Bindable和@Vetoable一切都变得简单多了吧!
http://mrhaki.blogspot.com/2009/08/groovy-goodness-bound-and-constrained.html