-
罪恶的成绩单
考试成绩单以及成绩排名,大家都懂得,以前上学的时候,这玩意往家里寄是真的要命。成绩单还需要家长签字,那么,我们来模拟将成绩单给家长签字这一情景,如图17-1:
我们先来看成绩单的抽象类SchoolReport,代码如下:
public abstract class SchoolReport {
//显示成绩情况
public abstract void report();
//要家长签字
public abstract void sign();
}
有抽象类了,在来看看具体的四年级成绩单FouthGradeSchoolReport,代码如下:
public class FouthGradeSchoolReport extends SchoolReport {
@Override
public void report() {
//成绩单的格式
System.out.println("尊敬的xxx家长:");
System.out.println("..............");
System.out.println("语文 62 数学 65 体育 98 自然 63");
System.out.println("..............:");
System.out.println("家长签名:");
}
@Override
public void sign(String name) {
System.out.println("家长签名:" + name);
}
}
成绩单出来62、65之类的在小学基本就是垫底,那么我们把成绩单带回去给家长看,修改一下类图,如图17-2:
这是原装的成绩单,没有动过的,来看Father类代码如下:
public class Father {
public static void main(String[] args) {
SchoolReport sc = new FouthGradeSchoolReport();
//看成绩单
sc.report();
//签名?休想,一顿胖揍
}
}
这个成绩直接拿出来肯定是找打,我们把成绩单封装一下,封装分为两步来实现:
- 汇报最高成绩
跟家长说各个科目的最高分,语文最高75,数学最高78,自然是80;那么家长一看你的成绩和最高分差不多,也还行;实际普遍在70以上,这个60多还是垫底 -
汇报排名
全班排第38名,但是不能说参加考试的只有40个人,反正成绩单上没写多少人,相比较之前的40多名还是有进步的,哈哈
这就是加上了一些修饰,我们看看类图如何修改,如图17-3:
这应该是常规的处理方式了,直接增加一个子类,覆写report方法,我们看具体的实现,代码如下:
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
private void reportHighScore(){
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
private void reportSort(){
System.out.println("我是排名第38名。。。");
}
@Override
public void report(){
this.reportHighScore();
super.report();
this.reportSort();
}
}
通过继承确实能够解决这个问题,但是这个只能作为应急的一种方式,如果需要继续加装饰那怎么办,而且装饰条件不是一次就明确的,可能过一段时间加一个,那一直通过继承来处理肯定是不合理的。在面向对象的设计中,如果超过两层,是不是该考虑设计的问题了,这是经验,不是绝对,继承的层次越多以后的维护成本越多,那这么解决这个装饰的问题呢,我们专门定义一批负责装饰的类,然后根据实际情况来决定是否需要进行装饰,类图如17-4:
增加一个抽象类和两个实现类,其中Decorator的作用是封装SchoolReport类,如果大家还记得代理模式,那么很容易看懂这个类图,装饰类的作用也是一个特殊的代理类,真实的执行者还是被代理的角色FouthGradeSchoolReport,代码如下:
public abstract class Decorator extends SchoolReport {
//首先得知道是哪个成绩单
private SchoolReport schoolReport;
public Decorator(SchoolReport schoolReport){
this.schoolReport = schoolReport;
}
@Override
public void report(){
this.schoolReport.report();
}
@Override
public void sign(String name){
this.schoolReport.sign(name);
}
}
装饰类还是把动作的执行委托给需要装饰的对象,Decorator抽象类的目的很简单,就是要让子类来封装SchoolReport的子类,先看HighScoreDecorator实现类,代码如下:
public class HighScoreDecorator extends Decorator {
public HighScoreDecorator(SchoolReport schoolReport) {
super(schoolReport);
}
private void reportHighScore(){
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
@Override
public void report() {
this.reportHighScore();
super.report();
}
}
重写了report方法,先调用具体装饰类的装饰方法reportHighScore,然后再调用具体构件的方法,我们再来看怎么汇报学校排序情况SortDecorator代码,代码如下:
public class SortDecorator extends Decorator {
public SortDecorator(SchoolReport schoolReport) {
super(schoolReport);
}
private void reportSort(){
System.out.println("我是排名第38名。。。");
}
@Override
public void report(){
super.report();
this.reportSort();
}
}
通过这两个强力的修饰工具,然后就可以拿一份"漂亮"的成绩单出来了,代码如下:
public class Client {
public static void main(String[] args) {
SchoolReport schoolReport;
schoolReport = new FouthGradeSchoolReport();
schoolReport = new HighScoreDecorator(schoolReport);
schoolReport = new SortDecorator(schoolReport);
schoolReport.report();
schoolReport.sign("老三");
}
}
这就是装饰者模式,可以处理一些用继承来处理的问题,但是比继承更加的灵活,但是装饰过多层之后就不太友好了,就像继承过多之后可读性就变的很差了。
-
装饰者模式的定义
装饰模式(Decorator Pattern)是一种比较常见的模式,其定义如下:Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)
类图17-5:
在类图中,有四个角色需要说明:
- Component抽象构件
Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象,如上面的成绩单。
注意 在装饰模式中,必然有一个最基本、最核心、最原始的接口或抽象类充当 Component抽象构件(这个抽象构件应该是描述我们想要装饰的部分) - ConcreteComponent具体构件
ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,就是我们要装饰的类,就是对应的例子中的FouthGradeSchoolReport
-Decorator装饰角色
一般是一个抽象类,实现接口或抽象方法,在他的属性里必然有一个private变量指向Component抽象构件。(装饰者模式的核心,和被装饰者实现或继承相同抽象,然后私有属性指向被装饰者)
-具体装饰角色
ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,这个就是用来装饰的了,想从哪个方面哪个角度去装饰,就实现Decorator去写自己的装饰方法就好了。
我们来看一下装饰者模式的通用代码:
public abstract class Component {
public abstract void operation();
}
public class ConcreateComponent extends Component {
@Override
public void operation() {
//需要被装饰的方法
System.out.println("do something");
}
}
public class Decorator extends Component {
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void operation() {
//委托给装饰者执行
component.operation();
}
}
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
private void method(){
//do something
}
@Override
public void operation() {
this.method();
super.operation();
}
}
(这个装饰者模式和代理模式非常相近,而且都是用来增强被装饰类(被代理类)的;但是这两者的具体的区别和用法又是怎么样的呢)
-
装饰者模式应用
3.1装饰者模式的优点
- 装饰类和被装饰类可以独立发展,而不会互相耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构建。
- 装饰者模式是继承模式的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。
- 装饰模式可以动态地扩展一个实现类的功能,而且还不会影响到被扩展的类,非常符合开闭原则
3.2装饰模式的缺点
装饰者模式虽然可以很好的替代继承的方式处理问题,并且比继承更加灵活 ,但是也同样带来了和继承类似的问题,那就是装饰层数过多的问题,这个就只能灵活处理;
3.3 装饰模式的使用场景 - 需要扩展一个类的功能,或给一个类增加附加功能;
- 需要动态地给一个对象增加功能,这些功能还可以动态的撤销;
- 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式
-
最佳实践
装饰模式是对继承的有力补充,而且装饰模式比继承更加的灵活,易维护,易扩展,衣服用;这个之前学习例子的时候就有很好的体会。
(这里有一点就是装饰模式和代理模式的对比,这一章中并没有提出,有兴趣可以百度一下;我们学完23种设计模式后,会接着学后续的相似的设计模式之前的对比。)
内容来自《设计模式之禅》