在正式学习4.2节关于委派的内容之前,我对于选择何种方式实现Lab3以提高代码的复用性感到十分困惑。直到我查阅到这样一篇博客:Java设计模式 — Decorator(装饰),Delegation(委托) ,Proxy(代理),有了一些想法。这篇博客中的例子对于理解装饰,委派和代理有很大的帮助,也能加深对课程中4.2节相关知识的理解,感兴趣的小伙伴可以直接阅读原博客,当然我下面也会依据我的理解进行改编。
我们先来一个生动形象的例子:众所周知,哈工大即将迎来百年校庆,届时会有许多校友回到母校参加。为了选择合适的交通工具来哈尔滨,需要建造一个票价查询系统。我们姑且把来哈尔滨的交通工具框定在火车和飞机这两种类型上面,但是会发现不管是火车还是飞机都有不同的舱位选择(比如飞机有头等舱,商务舱和经济舱)。如果火车和飞机各有三种仓位,组合一下就会出现六个票种。如果我们为每一个票种写一个单独的类,那么工程量巨大并且不可避免一些代码可复用性低造成的风险。因此,我们就需要进入我们的第一个主题,Decorator(装饰)。
一. Decorator(装饰)
装饰顾名思义就是要给一个我们已有的东西加一些点缀,使它与之前有所不同。例如一位校友在来哈尔滨的行程中选择了乘坐飞机的经济舱,但是他临时改变了主意,将经济舱升为了商务舱,这个升舱的过程就可以视作是他给他的机票进行了装饰。下面我们来看如果运用装饰来实现不同票价表示的例子。
我们先定义一个普通的火车票和机票
// 交通工具的接口
public interface Transportation {
public double price(); // 价格
}
// 普通的火车票
public class Train implements Transportation{
private final double trainPrice = 100;
@Override
public double price() {
return trainPrice;
}
}
// 普通的飞机票
public class Plane implements Transportation{
private final double planePrice = 500;
@Override
public double price() {
return planePrice;
}
}
接下里,我们要对普通的火车票和机票加一些装饰
// 装饰类
public abstract class TransDecorator implements Transportation {
protected Transportation trans;
public TransDecorator(Transportation SetTrans) {
trans = SetTrans;
}
@Override
public abstract double price();
}
// 头等舱
public class FirstClass extends TransDecorator{
private final double addRate = 1.0;
public FirstClass(Transportation SetTrans) {
super(SetTrans);
}
@Override
public double price() {
return trans.price() * (1+addRate); //升舱需要在原有票价基础上提高
}
}
// 商务舱
public class SecondClass extends TransDecorator{
private final double addRate = 0.5;
public SecondClass(Transportation SetTrans) {
super(SetTrans);
}
@Override
public double price() {
return trans.price() * (1+addRate); //升舱需要在原有票价基础上提高
}
}
于是,调用这些装饰,我们就能得到以下六类票:
Transportation normalPlane = new Plane(); // 经济舱飞机,500
Transportation secondClassPlane = new SecondClass(new Plane()); // 商务舱飞机,750
Transportation firstClassPlane = new FirstClass(new Plane()); // 头等舱飞机,1000
Transportation normalTrain = new Train(); // 普通火车,100
Transportation secondClassTrain = new SecondClass(new Train()); // 二等座火车,150
Transportation firstClassTrain = new FirstClass(new Train()); // 一等座火车,200
如果说,飞机和火车上还存在各种服务(比如火车上的餐车),就会出现更多种类的票价,我们可以通过加入更多的修饰来实现,这里我就不再枚举了。
那我们再回到刚才的例子中,这次我们把范围缩小一点,只取一个乘客进行讨论:HTY虽然不是校友,但是他也需要买票(什么时候能开学呐)。但是买票这种事情,不是说你上飞机的时候把钱拍到机长手里就可以的,而是要提前在网站上买好票。于是我们又要进入下一个主题,Delegation(委派)。
二. Delegation(委派)
这也是一个很生活化的词语。当有些事情我们不想做 做不到的时候,就需要伸手委托他人帮助,这一个行为我们称之为白嫖委派。例如上课举的例子:请律师打官司等等。 所以在刚才那个例子中,HTY作为一名乘客本身买不了机票,需要借助购票网站购买机票,也就是在使用委派的操作。下面我们来看代码是如何实现的。
// 乘客接口
public interface Passengers {
public double spend();
public void arrival();
}
// 一名乘客
public class Passenger implements Passengers{
private transPlotform plotform; // 委派购票平台购买
public Passenger(int trans, int category) {
plotform = new transPlotform(trans, category);
}
@Override
public double spend() {
return plotform.price();
}
@Override
public void arrival() {
System.out.printf("Arrive at HIT, spend ¥%.1f\n", spend());
}
}
// 购票平台
public class transPlotform implements Transportation {
Transportation trans;
public transPlotform(int SetTrans, int SetCategory) { // SetTrans = 0(火车)/1(飞机), SetCategory = 0(普通)/1(二等)/2(一等)
if (SetTrans == 0) trans = new Train();
else trans = new Plane();
if (SetCategory == 1) trans = new SecondClass(trans);
else if (SetCategory == 2) trans = new FirstClass(trans);
}
@Override
public double price() {
return trans.price();
}
}
于是,我们可以得到下面的结果:
Passengers hty = new Passenger(1, 1); // 买了一张商务舱的机票
hty.arrival();
// out: Arrive at HIT, spend ¥750.0
我们可以看到HTY本身并没有买票,而是委托购票平台买了票,他做的是支付购票的钱。
我们还是要回到刚才的例子中来:HTY比较穷,但是更不幸的是,他还很蠢,分不清自己的钱买得起什么票。于是他需要一个人告诉他,他当前买的票是否付得起。这里就要引出我们的最后一个主题,Proxy(代理)。
三. Proxy(代理)
代理也很好理解,例如很多国外的游戏公司只精通开发游戏,而怎么在中国卖游戏,他们并不擅长,于是就需要找某讯(没有打广告)等一些公司代替他们宣传和销售。当大家看完接下来的代码后,可能会有一个困惑,代理和委派好像是一样的。其实不然,仔细比较两者的代码,你会发现:代理本身什么也不做,只是在调用被代理类的方法;而委派,是要调用自己的方法解决客户需求的。(这么一看,是不是觉得代理商都是些薅羊毛的奸商呢
// 代理类
public class smartPassenger implements Passengers {
private Passenger passenger;
public smartPassenger(Passenger SetPass) {
passenger = SetPass;
}
@Override
public double spend() {
return 0;
}
@Override
public void arrival() {
if (passenger.spend() > 500) { //如果乘客购买的票贵于500
System.out.println("You don't have enough money! Stay at home!");
}
else {
passenger.arrival();
}
}
}
我们来看上面这个代理类能干什么
Passengers hty = new Passenger(1, 1); // HTY企图买一张商务舱机票
Passengers xxd = new smartPassenger((Passenger)hty); // 这时XXD突然出现,代替HTY决策一下该不该买这张票
xxd.arrival(); // 发现HTY并没有这么多钱,于是让他老老实实呆在了家里
hty = new Passenger(1, 0); // HTY只能重新选择了一张经济舱的机票
xxd = new smartPassenger((Passenger)hty); // XXD再次审核
xxd.arrival(); // 发现钱刚好够,于是HTY可以高高兴兴的回学校了
结论
以上讨论的Decorator(装饰),Delegation(委派),Proxy(代理)这三种操作,在实际的代码编写中具有很大的价值,我也只是根据上文给出的原博客进行了一些改编并加入自己的理解。至于深入理解乃至熟练运用,还需要在实践中多加练习,例如正在进行的Lab3,就有很多机会运用这些操作。我也会 可能在接下来的博客中介绍自己在设计Lab3中的一些想法,希望这些博客能给大家一些启发吧。