装饰模式的动机(Motivation)
- ➢在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- ➢如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?
案例:人及其属性
- 人有不同国家,如中国人、美国人, 如果有属性:高和富,则有组合: 高、富、高富等共3个属性描述。
- 如果有属性:高、富、帅,则有组合: 1、高、富、帅、 2、高富、高帅、富帅、 3、高富帅等共7个属性描述。
设n是国家的个数,m是属性个数,则需要的类的个数是:
如果n=2,m=2,则需要的类是1+222=9个;
如果n=2,m=3(高、富、帅),则需要的类是1+223=17个 ——用继承来扩展对象的功能导致子类的膨胀
普通实现:
//业务操作接口
abstract class Person{
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void desc();
}
//主体类:中国人、美国人两个类
class Chinese extends Person{
@Override
public void desc() {
// TODO Auto-generated method stub
System.out.println(name+"是中国人");
}
}
class American extends Person{
@Override
public void desc() {
// TODO Auto-generated method stub
System.out.println(name+"是美国人");
}
}
//扩展操作,对各国人进行操作
class highChinese extends Chinese{
public void desc() {
super.desc();
System.out.println("是high人");
}
}
class richChinese extends Chinese{
public void desc() {
super.desc();
System.out.println("是rich人");
}
}
class highrichChinese extends Chinese{
public void desc() {
super.desc();
System.out.println("是high&rich人");
}
}
class highAmerican extends American{
public void desc() {
super.desc();
System.out.println("是high人");
}
}
class richAmerican extends American{
public void desc() {
super.desc();
System.out.println("是rich人");
}
}
class highrichAmerican extends American{
public void desc() {
super.desc();
System.out.println("是high&rich人");
}
}
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
highChinese hc=new highChinese();
hc.setName("yaoming");
richChinese rc=new richChinese();
rc.setName("mayun");
highrichAmerican hra=new highrichAmerican();
hra.setName("tom");
hc.desc();
rc.desc();
hra.desc();
}
}
缺点
- 这样使用继承得到的结果往往是随着需求的变化,子类急剧增多,同时充斥着重复代码,这时候的关键是划清责任。
- 问题的原因:
- 除了其中的具体描述,例如:高个,富人等不同,发现所有的属性描述都是一样的方法,出现大量代码冗余。
改进版本一(组合代替继承)
//业务操作接口
abstract class Person{
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void desc();
}
//主体类:中国人、美国人两个类
class Chinese extends Person{
@Override
public void desc() {
// TODO Auto-generated method stub
System.out.println(name+"是中国人");
}
}
class American extends Person{
@Override
public void desc() {
// TODO Auto-generated method stub
System.out.println(name+"是美国人");
}
}
//扩展操作,对各国人进行操作
class highChinese {
Chinese person;//组合替代继承
public void desc() {
person.desc();
System.out.println("是high人");
}
public highChinese(Chinese person) {
this.person=person;
}
}
class richChinese {
Chinese person;//组合替代继承
public void desc() {
person.desc();
System.out.println("是rich人");
}
public richChinese(Chinese person) {
this.person=person;
}
}
class highrichChinese {
Chinese person;//组合替代继承
public void desc() {
person.desc();
System.out.println("是high&rich人");
}
public highrichChinese(Chinese person) {
this.person=person;
}
}
class highAmerican {
American person;//组合替代继承
public void desc() {
person.desc();
System.out.println("是high人");
}
public highAmerican(American person) {
this.person=person;
}
}
class richAmerican extends American{
American person;//组合替代继承
public void desc() {
person.desc();
System.out.println("是rich人");
}
public richAmerican(American person) {
this.person=person;
}
}
class highrichAmerican extends American{
American person;//组合替代继承
public void desc() {
person.desc();
System.out.println("是high&rich人");
}
public highrichAmerican(American person) {
this.person=person;
}
}
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Chinese p=new Chinese();
p.setName("yaoming");
highChinese hc=new highChinese(p);
p.setName("mayun");
richChinese rc=new richChinese(p);
American q=new American();
q.setName("tom");
highrichAmerican hra=new highrichAmerican(q);
hc.desc();
rc.desc();
hra.desc();
}
}
当一个变量的声明类型都是某个基类(Person)的子类(Chinese, American)的时候,就该将它声明为这个基类(Person),由于多态,可以使得它在未来(运行时)成为子类的对象。
//扩展操作,对各国人进行操作
class highChinese {
Person person;//基类代替子类,消除编译时的依赖
public void desc() {
person.desc();
System.out.println("是high人");
}
public highChinese(Person person) {
this.person=person;
}
}
。。。。。
发现: highChinese 与highAmerican,richChinese 与richAmerican,
highrichChinese 与highrichAmerican这3对类除了类名之外都相同,所以可以进行合并,分别变为:highPerson, richPerson, highrichPerson消除重复。
消除重复
//扩展操作,对各国人进行操作
class highPerson{
Person person;//基类代替子类,消除编译时的依赖
public void desc() {
person.desc();
System.out.println("是high人");
}
public highPerson(Person person) {
this.person=person;
}
}
class richPerson{
Person person;//基类代替子类,消除编译时的依赖
public void desc() {
person.desc();
System.out.println("是rich人");
}
public richPerson(Person person) {
this.person=person;
}
}
class highrichPerson {
Person person;//基类代替子类,消除编译时的依赖
public void desc() {
person.desc();
System.out.println("是high&rich人");
}
public highrichPerson(Person person) {
this.person=person;
}
}
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Chinese p=new Chinese();
p.setName("yaoming");
highPerson hc=new highPerson(p);
p.setName("mayun");
richPerson rc=new richPerson(p);
American q=new American();
q.setName("tom");
highrichPerson hra=new highrichPerson(q);
hc.desc();
rc.desc();
hra.desc();
}
}
发现问题:为了保证从继承转组合以后的抽象接口函数public void desc() 遵循接口规范,即继承基类设置的抽象接口函数public void desc() 。还是需要通过继承来完善接口规范,不过只需要继承基类Person。
//扩展操作,对各国人进行操作
class highPerson extends Person{//为实现接口继承基类Person
Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是high人");
}
public highPerson(Person person) {
this.person=person;
}
}
class richPerson extends Person{//为实现接口继承基类Person
Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是rich人");
}
public richPerson(Person person) {
this.person=person;
}
}
class highrichPerson extends Person{//为实现接口继承基类Person
Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是high&rich人");
}
public highrichPerson(Person person) {
this.person=person;
}
}
继续发现问题
根据重构:当类中含有重复字段和方法,应该将其提到基类中去。
这里,highPerson , richPerson 和highrichPerson三个类中都含有字段Person person,应该将其提到基类中去,但是,如果将Person person提到Person基类中去,会发现在Person类中会包含这个不需要的字段。
解决:设计一个中间基类。这样就引出了装饰模式。
改进版本二(使用装饰模式<中间基类>)
//中间类
abstract class DecoratorPerson extends Person{
Person person;//以聚合的方式来支持未来多态的变化
public DecoratorPerson(Person person) {
this.person=person;
}
}
//扩展操作,对各国人进行操作
class highPerson extends DecoratorPerson{//为避免重复,实现接口继承DecoratorPerson
//Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是high人");
}
public highPerson(Person person) {
super(person);
}
}
class richPerson extends DecoratorPerson{//为避免重复,实现接口继承DecoratorPerson
//Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是rich人");
}
public richPerson(Person person) {
super(person);
}
}
class highrichPerson extends DecoratorPerson{//为避免重复,实现接口继承DecoratorPerson
//Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是high&rich人");
}
public highrichPerson(Person person) {
super(person);
}
}
客户端更改之后:
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Chinese p=new Chinese();
p.setName("yaoming");
highPerson hc=new highPerson(p);
p.setName("mayun");
richPerson rc=new richPerson(p);
American q=new American();
q.setName("tom");
//highrichPerson hra=new highrichPerson(q);
hc.desc();
rc.desc();
//hra.desc();
highPerson hp=new highPerson(q);
richPerson rp=new richPerson(hp);
rp.desc();
}
}
于是,保留单个属性,而组合属性操作就可以删除了!
此时,无论是增加国家还是增加属性只需增加一个类。
- 设n是国家的个数,m是属性的个数,则需要的类的最终的个数是:
- 上面的n=2,m=2,所以需要类的个数是6个。
//扩展操作,对各国人进行操作,增加一个属性
class handsomePerson extends DecoratorPerson{//为避免重复,实现接口继承DecoratorPerson
//Person person;//基类代替子类,消除编译时的依赖
public void desc() {//继承基类Person就能保证实现接口
person.desc();
System.out.println("是handsome人");
}
public handsomePerson(Person person) {
super(person);
}
}
客户端改为:
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Chinese p=new Chinese();
p.setName("yaoming");
highPerson hc=new highPerson(p);
p.setName("mayun");
richPerson rc=new richPerson(p);
American q=new American();
q.setName("tom");
hc.desc();
rc.desc();
highPerson hp=new highPerson(q);
richPerson rp=new richPerson(hp);
rp.desc();
handsomePerson hsp=new handsomePerson(rc);
hsp.desc();
hc=new highPerson(hsp);
hc.desc();
}
}
装饰模式定义
动态的给一个对象添加一些额外的职责, 就增加功能来说, 装饰模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)
装饰类的代码实现:
//Component类
abstract class Component{
public abstract void Operation();
}
//ConcreteComponent类
class ConcreteComponent extends Component{
@Override
public void Operation() {
// TODO Auto-generated method stub
System.out.println("具体对象的操作");
}
}
//Decorator类
abstract class Decorator extends Component{//注意这里,装饰类也继承Component抽象类
protected Component component; //组合Component抽象类
public void SetComponent(Component component)
{ //设置Component
this.component = component;
}
public void Operation() //重写继承来的Operation()
{ //实际执行的是成员component的Operation()
if(component != null){
component.Operation();
}
}
}
//ConcreteDecoratorA类
class ConcreteDecoratorA extends Decorator{
private String addedState;//本类的独有的功能,以区别于ConcreteDecoratorB
public void Operation(){ //重写继承来的Operation()
//注意这个操作是抽象类Decorator中定义的操作Operation()
super.Operation(); //首先运行原Decorator的Operation(),再执行本类的功能
//如addedState,相当于对原Component进行了装饰
addedState = "New State";
System.out.println("具体装饰对象A的操作");
}
}
//ConcreteDecoratorB类
class ConcreteDecoratorB extends Decorator{
public void Operation(){//重写继承来的Operation()
//注意这个操作是抽象类Decorator中定义的操作Operation()
super.Operation(); //首先运行原Decorator的Operation(),再执行本类的功能
//如addedBehavior(),相当于对原Component进行了装饰
addedBehavior();
System.out.println("具体装饰对象B的操作");
}
private void addedBehavior(){ //本类独有的方法 ,区别于ConcreteDecoratorA
System.out.println("AddedBehavior Operation");
}
}
public class Main1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
d1.SetComponent(c);
d2.SetComponent(d1);
d2.Operation();
}
}
- 装饰模式就是利用setComponent来对对象进行包装的。
- 装饰模式常常被称为包裹模式,就是因为每一个具体装饰类都将下一个具体装饰类或者具体构件类包裹起来。
- 这样每个装饰对象的实现和如何使用这个对象分离开了。每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链中。
要点总结
- 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了Component类。
- Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
本质
动态组合:动态是手段,组合是目的
//以上部分摘取自朱红梅老师2020年5月的课件。