本文正在参与「金石方案」

虽然目前房价依旧很高,买房的人依旧许多。如果大家买的是毛坯房,无疑还有一项艰巨的任务要面临,那就是装修。对新房进行装修并没有改动房子用于居住的本质,但它能够让房子变得更美丽、更温馨、更有用、更能满足居家的需求。在软件规划中,咱们也有一种类似新房装修的技能能够对已有目标(新房)的功用进行扩展(装修),以取得愈加符合用户需求的目标,使得目标具有愈加强大的功用。这种技能对应于一种被称之为装修形式的规划形式,本章将介绍用于扩展体系功用的装修形式。

往期文章:

  • 五分钟完全了解制作者形式
  • 不调用结构函数,怎样创建目标?
  • 一文弄懂访问者形式
  • 怎样在事务开发中运用适配器形式?
  • 怎样通过桥接形式重构代码?
  • 怎样高雅的运用策略形式
  • 一文完全了解组合形式
  • 【规划形式系列】- 工厂形式
  • 【规划形式系列】- 单例形式
  • 秒懂 Java 的三种署理形式
  • 依靠倒置准则:高层代码和底层代码,究竟谁该依靠谁?
  • 接口阻隔准则:接口里的办法,你都用得到吗?
  • Liskov替换准则:用了承继,子类就规划对了吗?
  • 开放关闭准则:不改代码怎样写新功用?
  • 规划形式准则之:单一责任准则SRP

装修器形式概述

装修形式能够在不改动一个目标自身功用的基础上给目标添加额定的新行为,在现实生活中,这种状况也处处存在,例如一张相片,咱们能够不改动相片自身,给它添加一个相框,使得它具有防潮的功用,并且用户能够根据需要给它添加不同类型的相框,甚至能够在一个小相框的外面再套一个大相框。

装修形式是一种用于替代承继的技能,它通过一种无须界说子类的办法来给目标动态添加责任,运用目标之间的关联关系替代类之间的承继关系。在装修形式中引入了装修类,在装修类中既能够调用待装修的原有类的办法,还能够添加新的办法,以扩大原有类的功用。

装修形式结构图:

如何动态的给一个类增加功能?

  • Component(笼统构件):它是详细构件和笼统装修类的共同父类,声明晰在详细构件中完成的事务办法,它的引入能够使客户端以共同的办法处理未被装修的目标以及装修之后的目标,完成客户端的通明操作。

  • ConcreteComponent(详细构件):它是笼统构件类的子类,用于界说详细的构件目标,完成了在笼统构件中声明的办法,装修器能够给它添加额定的责任(办法)。

  • Decorator(笼统装修类):它也是笼统构件类的子类,用于给详细构件添加责任,可是详细责任在其子类中完成。它保护一个指向笼统构件目标的引证,通过该引证能够调用装修之前构件目标的办法,并通过其子类扩展该办法,以到达装修的意图。

  • ConcreteDecorator(详细装修类):它是笼统装修类的子类,担任向构件添加新的责任。每一个详细装修类都界说了一些新的行为,它能够调用在笼统装修类中界说的办法,并能够添加新的办法用以扩大目标的行为。

由于详细构件类和装修类都完成了相同的笼统构件接口,因而装修形式以对客户通明的办法动态地给一个目标附加上更多的责任,换言之,客户端并不会觉得目标在装修前和装修后有什么不同。装修形式能够在不需要创造更多子类的状况下,将目标的功用加以扩展。

简略事例

场景:气候太热了,喝点儿冰水解解暑;加点儿柠檬片,让果汁好喝点儿。

先界说一个喝水的接口

public interface Drink {
    /**
     * 喝水
     */
    void drink();
}

写一个接口的完成

public class DrinkWater implements Drink {
    @Override
    public void drink() {
        System.out.println("喝水");
    }
}

一个简略的装修器

public class DrinkDecorator implements Drink {
    private final Drink drink;
    public DrinkDecorator(Drink drink) {
        this.drink = drink;
    }
    @Override
    public void drink() {
        System.out.println("先加点儿柠檬片");
        drink.drink();
    }
}

测验

public class DrinkMain {
    public static void main(String[] args) {
        Drink drink = new DrinkWater();
        drink = new DrinkDecorator(drink);
        drink.drink();
    }
}

运行成果

先加点儿柠檬片
喝水

一个简略的装修器形式例子就写完了,这里仅仅先了解一下装修器形式。

事例场景

如何动态的给一个类增加功能?
大家应该都喝过奶茶吧,我经常喝的原味奶茶,里边能够添加配料,每添加一种配料,该奶茶的价格就会添加,要求核算一种奶茶的价格。

下面就能够运用装修器形式来模仿这种场景。

界说笼统构件类

/**
 * 奶茶
 */
public interface MilkyTea {
    double cost();
}

界说详细构件类

/**
 * 珍珠奶茶
 */
public class PearlMilkyTea implements MilkyTea{
    @Override
    public double cost() {
        return 13.0;
    }
}

界说笼统装修类

public abstract class MilkyTeaDecorator implements MilkyTea{
    private MilkyTea milkyTea;
    public MilkyTeaDecorator(MilkyTea milkyTea){
        this.milkyTea = milkyTea;
    }
    @Override
    public double cost() {
        return milkyTea.cost();
    }
}

界说详细装修类

/**
 * 椰果奶茶
 */
public class CoconutDecorator extends MilkyTeaDecorator{
    public CoconutDecorator(MilkyTea milkyTea) {
        super(milkyTea);
    }
    @Override
    public double cost() {
        System.out.println("添加椰果");
        return super.cost() + 2.0;
    }
}
/**
 * 布丁奶茶
 */
public class PuddingDecorator extends MilkyTeaDecorator{
    public PuddingDecorator(MilkyTea milkyTea) {
        super(milkyTea);
    }
    @Override
    public double cost() {
        System.out.println("添加布丁");
        return super.cost() + 1.5;
    }
}

测验代码

public class Client {
    public static void main(String[] args) {
        MilkyTea milkyTea = new PearlMilkyTea();
        milkyTea = new CoconutDecorator(milkyTea);
        milkyTea = new PuddingDecorator(milkyTea);
        System.out.println(milkyTea.cost());
    }
}

运行成果

添加布丁
添加椰果
16.5

在客户端代码中,咱们先界说了一个MilkyTea类型的详细构件目标milkyTea,然后将milkyTea作为结构函数的参数注入到详细装修类CoconutDecorator中,得到一个装修之后目标milkyTea,再将装修了一次之后的milkyTea目标注入另一个装修类PuddingDecorator中完成第二次装修,得到一个通过两次装修的目标milkyTea,再调用milkyTea的cost()办法即可得到一杯既加了布丁又加了椰果的珍珠奶茶价格。

装修器形式优缺点

优点

  1. 装修类和被装修类能够独立开展,不会相互耦合。
  2. 比较于承继,愈加的轻便、灵敏。
  3. 能够动态扩展一个完成类的功用,不用修改原本代码

缺点

  1. 会发生许多的装修类,添加了体系的复杂性。
  2. 这种比承继愈加灵敏机动的特性,也一起意味着装修形式比承继易于犯错,排错也很困难,关于屡次装修的目标,调试时寻觅错误或许需要逐级排查,较为繁琐。

装修器形式适用场景

  1. 在不影响其他目标的状况下,以动态、通明的办法给单个目标添加责任。

  2. 当不能采用承继的办法对体系进行扩展或者采用承继不利于体系扩展和保护时能够运用装修形式。不能采用承继的状况主要有两类:第一类是体系中存在很多独立的扩展,为支持每一种扩展或者扩展之间的组合将发生很多的子类,使得子类数目呈爆炸性增长;第二类是因为类已界说为不能被承继(如Java语言中的final类)。

总结

装修形式降低了体系的耦合度,能够动态添加或删除目标的责任,并使得需要装修的详细构件类和详细装修类能够独立变化,以便添加新的详细构件类和详细装修类。在软件开发中,装修形式使用较为广泛,例如在JavaIO中的输入流和输出流的规划、javax.swing包中一些图形界面构件功用的增强等地方都运用了装修形式。