代理
对象和对象之间除了组合和继承外,还有第三种关系,江湖人称代理。但是代理这种关系不是JAVA语言自带的,是组合和继承两者结合的一种特殊关系。代理中,我们需要把成员对象放到需要构造的类当中,不管是组合还是继承,我们在需要构造的类当中,会暴露这个成员对象的所有方法,所以代理看起来有点四不像。
我们来看一个车的例子,这样会更清楚一些:
//车的控制模块
public class S {
void up(int velocity) {}
void down(int velocity) {}
void left(int velocity) {}
void right(int velocity) {}
void forward(int velocity) {}
void back(int velocity) {}
void turboBoost() {}
}
我们使用继承来建造这个模块
public class C extends S {
private String name;
public C(String name) { this.name = name; }
public String toString() { return name;}
public static void main(String[] args) {
C c = newC(“cc”);
c.forward(100);
}
}
我们看到这个例子中,C虽然继承了S,拥有S的行为方法,但是C不是S,而且S里面的所有方法都在C里面彻底暴露了,这样会出现很多问题,但是如果使用代理,就能很好的解决这个问题。
我们来改造一下上面的例子,新增一个代理类SD:
public class S {
void up(int velocity) {}
void down(int velocity) {}
void left(int velocity) {}
void right(int velocity) {}
void forward(int velocity) {}
void back(int velocity) {}
void turboBoost() {}
}
public class SD {
private String name;
private S s = new S ();
public SD (String name) {
this.name = name;
}
void up(int velocity) {
s.up(velocity);
}
void down(int velocity) {
s .down(velocity);
}
void left(int velocity) {
s .left(velocity);
}
void right(int velocity) {
s .right(velocity);
}
void forward(int velocity) {
s . forward (velocity);
}
void back(int velocity) {
s . back (velocity);
}
void turboBoost(){
s . turboBoost (velocity);
}
public static void main(String[] args) {
C c = newC(“cc”);
c.forward(100);
}
}
我们看到,创建这个代理对象,首先需要在代理类中创建一个private的成员对象,然后把需要这个对象的一些方法赋给代理对象,这样就依然可以实现组合或者继承的效果,但是代理对象可以控制和选择成员对象里面的方法,这就减少了成员对象方法的暴露,提高了对代码的控制力。
组合和继承的结合
我们在实际开发过程中,通常不会是只使用一种关系,一般是组合和继承结合使用,我们来看一个例子,就清楚了两者结合使用的妙处。
import static net.mindview.util.Print.*;
class P {
P(int i) {
print(“P”);
}
}
class D extends P {
D(int i) {
super(i);
print(“D”);
}
}
class U {
U(int i) {
print(“U”);
}
}
class S extends U {
S(int i) {
super(i);
print(“S”);
}
}
class F extends U {
F(int i) {
super(i);
print(“F”);
}
}
class K extends U {
K(int i) {
super(i);
print(“K”);
}
}
class C {
custom(int i) {
print(“C”);
}
}
public class PS extends C {
private S s;
private F f;
private K k;
private D d;
public PS(int i) {
super(i+1);
s = new S(i+2);
s = new F(i+3);
s = new K(i+4);
s = new D(i+5);
print(“PS”);
public static void main(String[] args) {
PS ps = new PS(10);
}
}
}
/*Output:
C
U
S
F
U
K
P
D
PS
通过这个例子,我们看到,通过组合和继承的结合使用,可以节约大量的代码。
正确清理
我们之前学习的垃圾回收器都知道,我们没办法知道垃圾回收器什么时候会被调用,还有不知道它是否会被调用,这方面我们没办法控制,但是有时候我们需要明确地清理一些东西,这就需要通过显式的方式来进行清理,一般是将这个动作放到finally模块中去执行,这会有效避免异常的出现。
我们来通过一个例子来看一下:
class S {
S(int i) { print(“S”);}
void dispose() { print(“S”); }
}
class C extends S {
C (int i) {
super(i);
print(“DC”);
}
void dispose() {
print(“EC”);
super.dispose();
}
}
class T extends S {
T(int i) {
super(i);
print(“DT”);
}
void dispose() {
print(“ET”);
super.dispose();
}
}
class L extends S {
private int start, end;
L(int start, int end) {
super(start);
this.start = start;
this.end = end;
print(“DL:”+ start + “, ”+end);
}
void dispose() {
print(“EL:”+ start + “, ”+end);
super.dispose();
}
}
public class CAD extends S {
private C c;
private T t;
private L[] l = new L[3];
public CAD(int i) {
super(i + 1);
for(int j = 0; j < l.length; j++){
l[j] = new L(j,j*J);
}
c = new C(1);
t = new T(1);
print(“Combined”);
}
public void dispose() {
print(“CAD.dispose()”);
t.dispose();
t.dispose();
for(int i = l.length – 1; i >= 0; i--){
l[i].dispose();
}
super.dispose();
}
public static void main(String[] args) {
CAD cad = new CAD(47);
try {
…
}finally{
cad.dispose();
}
}
}
/*Output:
S
S
DL
S
DL
S
DL
S
DC
S
DT
C
CAD.dispose()
ET
S
EC
S
EL: 2,4
S
EL: 1,1
S
EL: 0,0
S
S
这个例子当中,每个类都重写了dispose()方法,而且通过super来调用基类的方法,每个类都拥有dispose()方法来清理内存资源。finally大括号里面的语句是必须要执行的,也就是说,必须要为cad对象调用dispose()方法来清理资源。我们需要注意的是,对基类和成员对象dispose()方法的调用顺序,从而避免互相互相依赖的现象发生,一般而言,清理的顺序和创建对象的顺序是相反的,如同例子中所示。
而且我们要知道dispose()方法用来手动释放资源,finalize()用来自动释放资源。
屏蔽方法名称
在Java基类中,是允许存在与基类中名称相同的方法,如果导出类的方法名称与基类中的方法名称相同,返回类型或者参数不同,那么就属于重载,如果导出类的方法名称与基类中的方法名称相同,返回类型或者参数也相同,那么就属于覆盖。我们来看一个重载的例子:
class H {
char do(char c) {
print(“do(char)”);
return ‘c’;
}
char do(float f) {
print(“do(float)”);
return 1.0f;
}
class M {}
class B extends H {
void do(M m) {
print(“do(M)”);
}
}
public class H {
public static void main(String[] args){
B b = new B();
b.d(1);
b.d(‘a’);
b.d(1.0f);
b.d(new M());
}
}
}
/*Output
do(float)
do(char)
do(float)
do(M)
如果我们想在导出类中覆盖基类的方法,我们可以在导出类的方法上面加上@Override注解,但是必须保证导出类的方法名称与基类中的方法名称相同,返回类型或者参数也相同,否则就会报错。当然不要这个注解也可以实现覆盖,但是这个注解可以防止我们把覆盖方法写成重载方法。