符合迪米特原则,即客户端只与外层接口打交道,不比关系底层实现细节。外观模式将底层的实现群体封装在一个方法中,并对外开放该方法。下图是典型的外观模式UML。
外观模式的缺点在于如果修改了底层实现,那么外观类相应的也要修改,不符合开闭原则,此时通过增加抽象类和扩展实现来解决。
与继承都可以扩展类的功能,但比继承灵活,装饰则可以动态地给某一个类添加一个功能,符合开闭原则,将类的核心功能和装饰功能区分开来。二者都会增加类的数量。
装饰者通常要将被装饰对象注入到自己的属性中,在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关系图,装饰者模式的精髓就在于,在保证调用类和调用方法不变的同时,扩展方法功能。
java中的InputStream、FileInputStream、BufferedInputStream就用到了装饰者模式,通过类的各种组合实现复杂的功能。
和装饰者模式类似,都是扩展对象的功能。不过装饰者注重的是功能的组合扩展,适配器模式注重的是功能转变。分为类继承和对象组合,前者通过实现接口并继承被适配类,即可实现功能的转变,后者是通过将被适配对象注入到适配者中,完成功能的扩展。不论哪种实现,在经过适配这一过程后,其调用方和方法名都发生了改变,因此无法像装饰者模式实现链式转换。
下图是类继承方式的UML,可见ConcretAdapt实现接口,并继承了被适配类,因此调用接口方法时,可通过super调用到被适配对象的方法。
下图是对象组合方式的UML,ConcretAdapt实现接口,内部引用对象指向被适配对象,完成功能转换。推荐使用这种方式,有利于减少类设计上的耦合。
对多个相同/相似对象的复用,诸如数据库连接池,日期工具类,文件句柄等,这些对象的复用,可以提升系统效率。缺点是由于这些对象群体一般采用容器存储,因此要考虑线程安全。
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,只不过目录又包含多个子目录或者课程,由于引用相同,因此可以采取同种方式进行动态调用。
提交评论