在[url=http://zjumty.iteye.com/blog/1885978]前面的文章[/url]中我們看到了GroovyBean的基本語法。除了能用少量的代碼實現JavaBean的功能以外,GroovyBean還提供了JavaBean標準裏的[url=http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html]綁定屬性和限制屬性[/url]。綁定屬性是一個屬性,當它的值發生變化是會通知其他的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