结构型

由 笔尖 发布

外观模式

符合迪米特原则,即客户端只与外层接口打交道,不比关系底层实现细节。外观模式将底层的实现群体封装在一个方法中,并对外开放该方法。下图是典型的外观模式UML。

image-20200615115718888

外观模式的缺点在于如果修改了底层实现,那么外观类相应的也要修改,不符合开闭原则,此时通过增加抽象类和扩展实现来解决。

装饰者模式

与继承都可以扩展类的功能,但比继承灵活,装饰则可以动态地给某一个类添加一个功能,符合开闭原则,将类的核心功能和装饰功能区分开来。二者都会增加类的数量。

装饰者通常要将被装饰对象注入到自己的属性中,在function A中调用被装饰对象的方法,且A与该方法同名。

举个例子,如下是Acake抽象类,用于扩展具体的蛋糕子类以及装饰器类。

public abstract class ACake {
    public abstract String getDesc();
    public abstract int getPrice();
}

其中一个具体的蛋糕实现类。

public class Cake extends ACake{
    @Override
    public String getDesc() {
        return "蛋糕";
    }
    @Override
    public int getPrice() {
        return 5;
    }
}

这是装饰器抽象类,继承了ACake,通过构造函数将ACake具体实现类传入其中。

public abstract class ACakedecorator extends ACake {
    protected ACake aCake;
    public ACakedecorator(ACake aCake) {
        this.aCake = aCake;
    }
}

其中一个装饰器实现类,通过实现getDesc方法和getPrice方法,便可在ACake实现类实现具体功能时使用到装饰器装饰的功能,该装饰器CakewithEgg装饰的功能是描述添加"with egg"和价格+2。

public class CakewithEgg extends ACakedecorator {
    public CakewithEgg(ACake aCake) {
        super(aCake);
    }
    @Override
    public String getDesc() {
        return aCake.getDesc() + "with egg";
    }
    @Override
    public int getPrice() {
        return aCake.getPrice() + 2;
    }
}

测试中,创建好具体子类,将aCake传入装饰器中,由于装饰器也是继承自ACake,因此可以当作一个实现类传入到其他装饰器中,这种“链式传递”就可以灵活的实现功能扩展。

public static void main(String[] args) {
    ACake aCake = new Cake();
    aCake = new CakewithEgg(aCake);
    aCake = new Cakewithmilk(aCake);
    System.out.println(aCake.getDesc());
    System.out.println(aCake.getPrice());
}

下图是UML关系图,装饰者模式的精髓就在于,在保证调用类和调用方法不变的同时,扩展方法功能。

image-20200615205550057

java中的InputStream、FileInputStream、BufferedInputStream就用到了装饰者模式,通过类的各种组合实现复杂的功能。

适配器模式

和装饰者模式类似,都是扩展对象的功能。不过装饰者注重的是功能的组合扩展,适配器模式注重的是功能转变。分为类继承和对象组合,前者通过实现接口并继承被适配类,即可实现功能的转变,后者是通过将被适配对象注入到适配者中,完成功能的扩展。不论哪种实现,在经过适配这一过程后,其调用方和方法名都发生了改变,因此无法像装饰者模式实现链式转换。

下图是类继承方式的UML,可见ConcretAdapt实现接口,并继承了被适配类,因此调用接口方法时,可通过super调用到被适配对象的方法。

image-20200617102945777

下图是对象组合方式的UML,ConcretAdapt实现接口,内部引用对象指向被适配对象,完成功能转换。推荐使用这种方式,有利于减少类设计上的耦合。

image-20200617103120307

享元模式

对多个相同/相似对象的复用,诸如数据库连接池,日期工具类,文件句柄等,这些对象的复用,可以提升系统效率。缺点是由于这些对象群体一般采用容器存储,因此要考虑线程安全。

Java中的Integer就采用了享元模式,如果>=-127&&<128,那么就会从缓存中取出已有的Integer对象,否则创建新的对象。

public class test {
    public static void main(String[] args) {
        Integer a = Integer.valueOf(127);
        Integer b = Integer.valueOf(127);
        System.out.println(a == b);//true

        a = Integer.valueOf(130);
        b = Integer.valueOf(130);
        System.out.println(a == b);//false
    }
}

组合模式

类中含有一个容器,其泛型又是本类,类似于树形结构,通常组合关系为一对多,不同层级调用同一方法时其动作可能不相同。典型的包括二叉树的遍历。

如图示课程的组合UML,Course和Catalog都属于CatalogCourse,只不过目录又包含多个子目录或者课程,由于引用相同,因此可以采取同种方式进行动态调用。

image-20200617135042168


暂无评论

发表评论