结构型模式描述如何将类或对象按照某种布局,组装成更大的结构,它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

结构型模式分为以下 7 种:

  1. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
  2. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
  3. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
  4. 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
  5. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
  6. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
  7. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

1.代理模式

在一些情况下,客户不希望直接去接触某些人, 这个时候就会委托中介找房子啥的, 这个中介就是代理

代理模式的定义与特点

在某些情况下,访问对象不能直接去访问目标对象,需要提供一个代理给访问对象去访问,然后在代理类里去调用那个被代理的方法,同时在那个方法周围还可以加一些功能AOP也是代理
代理模式的主要优点有:
1.代理模式在客户端与目标对象之间起到一个中介作用和保护对象作用
2.代理对象可以扩展目标对象的功能
3.代理模式将客户端与对象分离,在一定程度上降低系统的耦合度,方便扩展

缺点
1.类会变多
2.多了一个代理,响应速度会变慢
3.系统复杂度提升
那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式

代理模式的结构与实现

代理的结构比较简单, 代理跟真实主题都实现了抽象主题,通过调用代理来访问真实主题,也就是说没有采用代理的时候,直接访问一个接口的实现类来操作,但是有代理以后, 代理类也实现了那个接口,只是代理类里面会再调用真正的那个接口的实现类

代理模式的主要角色如下。

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

结构型模式特点及分类 - 图1
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。
根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态:在程序运行时,运用反射机制动态创建而成

    模式的实现

    1. package proxy;
    2. public class ProxyTest {
    3. public static void main(String[] args) {
    4. Proxy proxy = new Proxy();
    5. proxy.Request();
    6. }
    7. }
    8. //抽象主题
    9. interface Subject {
    10. void Request();
    11. }
    12. //真实主题
    13. class RealSubject implements Subject {
    14. public void Request() {
    15. System.out.println("访问真实主题方法...");
    16. }
    17. }
    18. //代理
    19. class Proxy implements Subject {
    20. private RealSubject realSubject;
    21. public void Request() {
    22. if (realSubject == null) {
    23. realSubject = new RealSubject();
    24. }
    25. preRequest();
    26. realSubject.Request();
    27. postRequest();
    28. }
    29. public void preRequest() {
    30. System.out.println("访问真实主题之前的预处理。");
    31. }
    32. public void postRequest() {
    33. System.out.println("访问真实主题之后的后续处理。");
    34. }
    35. }

    代理模式的应用场景

    当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

前面分析了代理模式的结构与特点,现在来分析以下的应用场景。

  • 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  • 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  • 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
  • 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  • 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。

  1. 真实主题与代理主题一一对应,增加真实主题也要增加代理。
  2. 设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如 SpringAOP,其结构图如图 4 所示。

结构型模式特点及分类 - 图2

2.适配器模式

在我们生活中经常出现两个接口不适配的情况,例如 直流电机需要电源适配器才能连到交流电源上, 再例如usb跟mac的雷利接口不兼容,需要使用扩展坞
在代码中我们也会遇到这种情况, 有的时候需要扩展模块, 代码库中已经有了现成的模块,但是跟我们现在的接口缺不相匹配,如果我们重新开发这个功能模块的话,又太浪费时间了, 这个时候就需要用适配器模式来完成这个需求

模式的定义与特点

适配器模式(Adapter)的定义如下:将一个已知的的类的接口转换成一个客户希望的另一个接口,使得原本因接口不兼容而不能一起工作的两个类可以一起工作
我理解的适配器,就是能让一个接口的功能是另外一个接口的功能, 这两个接口原来没有关系,然后用中间类也就是适配器类去调用那个已存在的功能(通过继承或聚合来调用), 然后去实现你这个要加功能的接口 , 接口->实现类(调用现有的功能)->进而达到访问这个接口就能访问那个已有的功能
适配器模式也分为类适配器模式跟对象适配器模式, 类适配器模式比对象适配器模式的耦合性更强一点,且要求程序员对程序的内部结构了解,所以类适配器用的更少

该模式的主要优点如下。

  • 客户端可以通过适配器透明地调用接口
  • 代码可以复用
  • 降低耦合性
  • 在很多业务场景下满足开闭原则

其缺点是:

  • 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
  • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

    模式的结构与实现

    Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。现在来介绍它们的基本结构

1. 模式的结构

1. 模式的结构

适配器模式(Adapter)包含以下主要角色。

  1. 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  2. 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  3. 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式的结构图如图 1 所示。
结构型模式特点及分类 - 图3

对象适配器模式的结构图如图 2 所示。

结构型模式特点及分类 - 图4

2. 模式的实现

适配器模式的代码如下。

  1. package adapter;
  2. //目标接口
  3. interface Target
  4. {
  5. public void request();
  6. }
  7. //适配者接口
  8. class Adaptee
  9. {
  10. public void specificRequest()
  11. {
  12. System.out.println("适配者中的业务代码被调用!");
  13. }
  14. }
  15. //类适配器类
  16. class ClassAdapter extends Adaptee implements Target
  17. {
  18. public void request()
  19. {
  20. specificRequest();
  21. }
  22. }
  23. //客户端代码
  24. public class ClassAdapterTest
  25. {
  26. public static void main(String[] args)
  27. {
  28. System.out.println("类适配器模式测试:");
  29. Target target = new ClassAdapter();
  30. target.request();
  31. }
  32. }
  33. package adapter;
  34. //对象适配器类
  35. class ObjectAdapter implements Target
  36. {
  37. private Adaptee adaptee;
  38. public ObjectAdapter(Adaptee adaptee)
  39. {
  40. this.adaptee=adaptee;
  41. }
  42. public void request()
  43. {
  44. adaptee.specificRequest();
  45. }
  46. }
  47. //客户端代码
  48. public class ObjectAdapterTest
  49. {
  50. public static void main(String[] args)
  51. {
  52. System.out.println("对象适配器模式测试:");
  53. Adaptee adaptee = new Adaptee();
  54. Target target = new ObjectAdapter(adaptee);
  55. target.request();
  56. }
  57. }

模式的应用场景

适配器模式(Adapter)通常适用于以下场景。

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

模式的扩展

适配器模式(Adapter)可扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口,其结构图如图 4 所示。

结构型模式特点及分类 - 图5

  1. package adapter;
  2. //目标接口
  3. interface TwoWayTarget
  4. {
  5. public void request();
  6. }
  7. //适配者接口
  8. interface TwoWayAdaptee
  9. {
  10. public void specificRequest();
  11. }
  12. //目标实现
  13. class TargetRealize implements TwoWayTarget
  14. {
  15. public void request()
  16. {
  17. System.out.println("目标代码被调用!");
  18. }
  19. }
  20. //适配者实现
  21. class AdapteeRealize implements TwoWayAdaptee
  22. {
  23. public void specificRequest()
  24. {
  25. System.out.println("适配者代码被调用!");
  26. }
  27. }
  28. //双向适配器
  29. class TwoWayAdapter implements TwoWayTarget,TwoWayAdaptee
  30. {
  31. private TwoWayTarget target;
  32. private TwoWayAdaptee adaptee;
  33. public TwoWayAdapter(TwoWayTarget target)
  34. {
  35. this.target=target;
  36. }
  37. public TwoWayAdapter(TwoWayAdaptee adaptee)
  38. {
  39. this.adaptee=adaptee;
  40. }
  41. public void request()
  42. {
  43. adaptee.specificRequest();
  44. }
  45. public void specificRequest()
  46. {
  47. target.request();
  48. }
  49. }
  50. //客户端代码
  51. public class TwoWayAdapterTest
  52. {
  53. public static void main(String[] args)
  54. {
  55. System.out.println("目标通过双向适配器访问适配者:");
  56. TwoWayAdaptee adaptee=new AdapteeRealize();
  57. TwoWayTarget target=new TwoWayAdapter(adaptee);
  58. target.request();
  59. System.out.println("-------------------");
  60. System.out.println("适配者通过双向适配器访问目标:");
  61. target=new TargetRealize();
  62. adaptee=new TwoWayAdapter(target);
  63. adaptee.specificRequest();
  64. }
  65. }

3.桥接模式

正常车有很多中 有电动的,烧油的, 有白的 黄的,黑的 各种颜色的,如果只用集成的话, 那子类太多太多了,不好拓展, 而且耦合性太强了,比如烧电的大众车,只有白色黑色,现在要新搞出一个烧电的宝马车,红黑蓝的, 那样又得修改好多代码啊

因此就有了桥接模式, 桥接模式就能将抽象与现实分离,使他们可以独立变化, 说白了 就是用组合/聚合来代替集成关系

桥接(Bridge)模式的优点是:

  • 抽象与实现分离,扩展能力强
  • 符合开闭原则
  • 符合合成复用原则
  • 其实现细节对客户透明

缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度

桥接模式的结构与实现

可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。

1. 模式的结构

桥接(Bridge)模式包含以下主要角色。

  1. 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。例如汽车
  2. 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。例如大众汽车
  3. 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。例如颜色
  4. 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。 黄色

结构型模式特点及分类 - 图6

2. 模式的实现

桥接模式的代码如下:

  1. package bridge;
  2. public class BridgeTest {
  3. public static void main(String[] args) {
  4. Car car = new DAZHONG_Car();
  5. Color col = new Yellow(car);
  6. col.whatColor();
  7. }
  8. }
  9. //实现化角色
  10. interface Car {
  11. public void carMsg();
  12. }
  13. //具体实现化角色
  14. class DAZHONG_Car implements Car {
  15. public void carMsg() {
  16. System.out.println("这是一台大众汽车");
  17. }
  18. }
  19. //抽象化角色
  20. abstract class Color {
  21. protected Car car;
  22. protected Abstraction(Car car) {
  23. this.car = car;
  24. }
  25. public abstract void whatColor();
  26. }
  27. //扩展抽象化角色
  28. class Yellow extends Color {
  29. protected Yellow(Car car) {
  30. super(car);
  31. }
  32. public void whatColor() {
  33. System.out.println("这个车是黄色的");
  34. car.carMsg();
  35. }
  36. }

4.装饰模式

顾名思义,在生活中虽然房子能住,但是我们会为了住着舒服去装修什么的, 这就是装饰
在代码中也是, 有一些模块只能实现最核心的功能, 在不改变其结构的前提下可以进行功能拓展

装饰模式的定义与特点

定义:在不改变对象结构的前提下,动态的给对象添加功能
也就是 要扩展一个接口的实现类的功能, 但不直接改那个接口的实现类
也就是说,接口A的实现类Aimpl的功能是1+1
但是现在想让Aimpl能在1+1的基础上再算11 但是却并不想改变Aimpl的代码
创建一个Bimpl来实现A, 在方法中调用Aimpl的1+1方法, 同时通过Bimpld的子类来加1
1的功能(当然也可以不用这个子类,而是直接去在Bimpl里加1*1方法)

优点:
1.完全满足开闭原则
2.装饰器是集成的补充,比集成更灵活,不改变对象的结构却能动态的增加功能

其主要缺点是:装饰模式会增加许多子类,过度使用会增加程序得复杂性。

装饰模式的结构与实现

1. 模式的结构

装饰模式主要包含以下角色。

  1. 抽象构件(Component)角色:被装饰的接口
  2. 具体构件(ConcreteComponent)角色:上面的那个接口的实现类, 会有一些小功能
  3. 抽象装饰(Decorator)角色:被装饰接口的实现类, 功能引用了具体构件的功能
  4. 具体装饰(ConcreteDecorator)角色:集成抽象装饰, 添加一些装饰功能。
    1. package decorator;
    2. public class DecoratorPattern {
    3. public static void main(String[] args) {
    4. Component p = new ConcreteComponent();
    5. p.operation();
    6. System.out.println("---------------------------------");
    7. Component d = new ConcreteDecorator(p);
    8. d.operation();
    9. }
    10. }
    11. //抽象构件角色
    12. interface Component {
    13. public void operation();
    14. }
    15. //具体构件角色
    16. class ConcreteComponent implements Component {
    17. public ConcreteComponent() {
    18. System.out.println("创建具体构件角色");
    19. }
    20. public void operation() {
    21. System.out.println("调用具体构件角色的方法operation()");
    22. }
    23. }
    24. //抽象装饰角色
    25. class Decorator implements Component {
    26. private Component component;
    27. public Decorator(Component component) {
    28. this.component = component;
    29. }
    30. public void operation() {
    31. component.operation();
    32. }
    33. }
    34. //具体装饰角色
    35. class ConcreteDecorator extends Decorator {
    36. public ConcreteDecorator(Component component) {
    37. super(component);
    38. }
    39. public void operation() {
    40. super.operation();
    41. addedFunction();
    42. }
    43. public void addedFunction() {
    44. System.out.println("为具体构件角色增加额外的功能addedFunction()");
    45. }
    46. }

    装饰模式的应用场景

前面讲解了关于装饰模式的结构与特点,下面介绍其适用的应用场景,装饰模式通常在以下几种情况使用。

  • 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

装饰模式的扩展

装饰模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的,如以下两种情况。
(1) 如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件,其结构图如图 4 所示。

结构型模式特点及分类 - 图7

(2) 如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并,其结构图如图 5 所示。

结构型模式特点及分类 - 图8

5.外观模式

在生活中, 综合农贸市场就是外观模式, 里面有卖肉的,卖蔬菜的, 一个市场包含功能一应俱全
代码中的外观模式也一样, 一个高级接口可以包含很多个子功能, 这样使用起来很方便

外观模式的定义与特点

外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

说白了就是把接口聚合到一个接口,然后就统一调用这个接口就能访问其他被聚合的接口了

在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

外观(Facade)模式的主要缺点如下。

  1. 不能很好地限制客户使用子系统类,很容易带来未知风险。
  2. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

    外观模式的结构与实现

    外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。

结构型模式特点及分类 - 图9

模式的实现

  1. package facade;
  2. public class FacadePattern {
  3. public static void main(String[] args) {
  4. Facade f = new Facade();
  5. f.method();
  6. }
  7. }
  8. //外观角色
  9. class Facade {
  10. private SubSystem01 obj1 = new SubSystem01();
  11. private SubSystem02 obj2 = new SubSystem02();
  12. private SubSystem03 obj3 = new SubSystem03();
  13. public void method() {
  14. obj1.method1();
  15. obj2.method2();
  16. obj3.method3();
  17. }
  18. }
  19. //子系统角色
  20. class SubSystem01 {
  21. public void method1() {
  22. System.out.println("子系统01的method1()被调用!");
  23. }
  24. }
  25. //子系统角色
  26. class SubSystem02 {
  27. public void method2() {
  28. System.out.println("子系统02的method2()被调用!");
  29. }
  30. }
  31. //子系统角色
  32. class SubSystem03 {
  33. public void method3() {
  34. System.out.println("子系统03的method3()被调用!");
  35. }
  36. }

6.享元模式

在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。

享元模式的定义与特点

享元模式是运用共享技术实现对细颗粒度的对象复用,从而提高资源利用率

享元模式的主要特点:相同的对象只保存一份

享元模式说白了就是相同的对象只保存一份,来省资源
相同的对象存在享元工厂的map里面,达到缓存,随取随拿, key是对象关键字,value是那个对象,然后非享元对象(不重复的对象)作为参数传到享元对象的方法里

缺点:
1.为了共享,需要将一些不能共享的对象外部状态化,增加了程序的复杂性
2.读取享元模式的外部状态会耗费一些时间

享元模式的结构与实现

享元模式有两个要求, 一个是细颗粒度,第二个就是可共享
因为颗粒度很细, 我们就会把对象信息分为内部状态跟外部状态信息

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变;
  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

享元模式本质其实就是缓存共享对象,降低内存消耗

1. 模式的结构

  • 抽象享元角色(Flyweight)就是一个提供给外部的接口,非享元的外部状态会以参数的形式传入这个接口
  • 具体享元(Concrete Flyweight)角色: 就是上面那个接口的实现类
  • 非享元角色: 就是非共享的信息 ,抽象享元的入参
  • 享元工厂: 责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

结构型模式特点及分类 - 图10

  1. public class FlyweightPattern {
  2. public static void main(String[] args) {
  3. FlyweightFactory factory = new FlyweightFactory();
  4. Flyweight f01 = factory.getFlyweight("a");
  5. Flyweight f02 = factory.getFlyweight("a");
  6. Flyweight f03 = factory.getFlyweight("a");
  7. Flyweight f11 = factory.getFlyweight("b");
  8. Flyweight f12 = factory.getFlyweight("b");
  9. f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
  10. f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
  11. f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
  12. f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
  13. f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
  14. }
  15. }
  16. //非享元角色
  17. class UnsharedConcreteFlyweight {
  18. private String info;
  19. UnsharedConcreteFlyweight(String info) {
  20. this.info = info;
  21. }
  22. public String getInfo() {
  23. return info;
  24. }
  25. public void setInfo(String info) {
  26. this.info = info;
  27. }
  28. }
  29. //抽象享元角色
  30. interface Flyweight {
  31. public void operation(UnsharedConcreteFlyweight state);
  32. }
  33. //具体享元角色
  34. class ConcreteFlyweight implements Flyweight {
  35. private String key;
  36. ConcreteFlyweight(String key) {
  37. this.key = key;
  38. System.out.println("具体享元" + key + "被创建!");
  39. }
  40. public void operation(UnsharedConcreteFlyweight outState) {
  41. System.out.print("具体享元" + key + "被调用,");
  42. System.out.println("非享元信息是:" + outState.getInfo());
  43. }
  44. }
  45. //享元工厂角色
  46. class FlyweightFactory {
  47. private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
  48. public Flyweight getFlyweight(String key) {
  49. Flyweight flyweight = (Flyweight) flyweights.get(key);
  50. if (flyweight != null) {
  51. System.out.println("具体享元" + key + "已经存在,被成功获取!");
  52. } else {
  53. flyweight = new ConcreteFlyweight(key);
  54. flyweights.put(key, flyweight);
  55. }
  56. return flyweight;
  57. }
  58. }
  59. 运行结果
  60. 具体享元a被创建!
  61. 具体享元a已经存在,被成功获取!
  62. 具体享元a已经存在,被成功获取!
  63. 具体享元b被创建!
  64. 具体享元b已经存在,被成功获取!
  65. 具体享元a被调用,非享元信息是:第1次调用a
  66. 具体享元a被调用,非享元信息是:第2次调用a
  67. 具体享元a被调用,非享元信息是:第3次调用a
  68. 具体享元b被调用,非享元信息是:第1次调用b
  69. 具体享元b被调用,非享元信息是:第2次调用b

享元模式的应用场景

当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。

享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。

前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

    7.组合模式

    在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜、以及厨房中的锅碗瓢盆等。

    组合模式的定义与特点

    组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式

结构型模式特点及分类 - 图11
组合模式中会把树枝节点跟叶子节点看作同一种数据类型,让他们具有一致的行为

说白了就是, 叶子跟树枝节点都是根节点的实现类,只是树枝节点里能依赖叶子节点,叶子结点对象作为参数传到树枝中 ,树枝里能存叶子节点对象,进而能调用其方法

组合模式的主要优点有:

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能;

    组合模式的结构与实现

    1. 模式的结构

  4. 抽象构件: 它的主要作用是为树叶构件和树枝构件声明公共接口,也就是客户通过它来访问树叶跟叶子

  5. 树叶构件: 没有子节点
  6. 树枝构件: 有子节点

组合模式分为透明式的组合模式和安全式的组合模式。
透明模式就是:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。其结构图如图 1 所示。
结构型模式特点及分类 - 图12

安全模式:只有树枝构建才有Add()、Remove() 及 GetChild() 方法

结构型模式特点及分类 - 图13

2. 模式的实现

假如要访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其对应的树状图如图 3 所示。

结构型模式特点及分类 - 图14
透明组合模式

  1. public class CompositePattern {
  2. public static void main(String[] args) {
  3. Component c0 = new Composite();
  4. Component c1 = new Composite();
  5. Component leaf1 = new Leaf("1");
  6. Component leaf2 = new Leaf("2");
  7. Component leaf3 = new Leaf("3");
  8. c0.add(leaf1);
  9. c0.add(c1);
  10. c1.add(leaf2);
  11. c1.add(leaf3);
  12. c0.operation();
  13. }
  14. }
  15. //抽象构件
  16. interface Component {
  17. public void add(Component c);
  18. public void remove(Component c);
  19. public Component getChild(int i);
  20. public void operation();
  21. }
  22. //树叶构件
  23. class Leaf implements Component {
  24. private String name;
  25. public Leaf(String name) {
  26. this.name = name;
  27. }
  28. public void add(Component c) {
  29. }
  30. public void remove(Component c) {
  31. }
  32. public Component getChild(int i) {
  33. return null;
  34. }
  35. public void operation() {
  36. System.out.println("树叶" + name + ":被访问!");
  37. }
  38. }
  39. //树枝构件
  40. class Composite implements Component {
  41. private ArrayList<Component> children = new ArrayList<Component>();
  42. public void add(Component c) {
  43. children.add(c);
  44. }
  45. public void remove(Component c) {
  46. children.remove(c);
  47. }
  48. public Component getChild(int i) {
  49. return children.get(i);
  50. }
  51. public void operation() {
  52. for (Object obj : children) {
  53. ((Component) obj).operation();
  54. }
  55. }
  56. }
  57. 执行结果为
  58. 树叶1:被访问!
  59. 树叶2:被访问!
  60. 树叶3:被访问!

安全模式
首先修改 Component 代码,只保留层次的公共行为。

  1. interface Component {
  2. public void operation();
  3. }

然后修改客户端代码,将树枝构件类型更改为 Composite 类型,以便获取管理子类操作的方法。

  1. public class CompositePattern {
  2. public static void main(String[] args) {
  3. Composite c0 = new Composite();
  4. Composite c1 = new Composite();
  5. Component leaf1 = new Leaf("1");
  6. Component leaf2 = new Leaf("2");
  7. Component leaf3 = new Leaf("3");
  8. c0.add(leaf1);
  9. c0.add(c1);
  10. c1.add(leaf2);
  11. c1.add(leaf3);
  12. c0.operation();
  13. }
  14. }