精华内容
最热下载
问答
  • 5星
    98.65MB Johnho130 2021-03-11 23:23:09
  • 5星
    178.06MB guoruibin123 2021-06-25 13:54:42
  • 5星
    37.09MB GJZGRB 2021-03-03 21:00:00
  • 5星
    293.1MB GJZGRB 2021-03-08 21:24:55
  • 5星
    290.12MB GZXGYZ 2021-03-18 21:26:10
  • 5星
    619KB u013883025 2021-08-19 00:49:44
  • 5星
    29KB jia666666 2021-09-23 16:04:09
  • 5星
    389B qq_23930765 2021-09-30 08:42:39
  • 5星
    335.56MB yioye 2021-03-30 11:07:03
  • 5星
    3.01MB bala5569 2021-03-17 09:08:55
  • 3KB weixin_38654855 2021-06-11 20:13:51
  • 在做项目时需要设置程序的兼容模式选项,而网上几乎没有相关的例程,所以做了一个[程序兼容性设置]的函数,支持设置:兼容版本、简化颜色模式、用640X480分辨率运行、禁用全屏优化、管理员运行、启用此程序以使用...

    简介:

    在做项目时需要设置程序的兼容模式选项,而网上几乎没有相关的例程,所以做了一个[程序兼容性设置]的函数,支持设置:兼容版本、简化的颜色模式、用640X480分辨率运行、禁用全屏优化、管理员运行、启用此程序以使用onedrive文件、代替DPI缩放、程序DPI也就是[右键-属性-兼容模式]下的所有的选项都可以通过本函数设置
    一般情况下设置的是win7+管理员运行的配置,故此也附带了一个仅6行代码的[设置管理员运行与兼容模式win7]的函数


    网盘下载地址:

    http://kekewl.org/JUYgKDMsTCG0


    图片:


    展开全文
    jianjian678 2021-05-03 17:21:59
  • 虽然保留了位某程序缩减到16位色的运行模式,不过我们只要选择红警2,右键属性,然后勾上简化颜色模式,选择16位色运行,但是大家要注意,这是由32位简化而来的16位色,是以模拟的方式运行,效果大家可想而知:不...

    一般来说我们的电脑装上红警2资源,都不需要任何设置就可以顺利运行,不过到了Win8就变得麻烦了,由于微软将16位色从Win8上移除了,导致了红警2在Win8普通状态下难以兼容。虽然保留了位某程序缩减到16位色的运行模式,不过我们只要选择红警2,右键属性,然后勾上简化的颜色模式,选择16位色运行,但是大家要注意,这是由32位简化而来的16位色,是以模拟的方式运行,效果大家可想而知:不到5帧。那么ghost win8系统为例又该如何去解决不能兼容红警2游戏的问题呢?

    6f539481100ae3b79bc0b8d6b7a24442.png

    玩红警2鼠标指针一旦成动画形态,将会有明显的停顿。鼠标指针一旦成动画形态,将会有明显的停顿。碰到这种情况我们可以使用VideoBackBuffer语句,使得全屏下拥有窗口化下一样的效果。还有一个是微软KB2756872补丁,解决了鼠标指针的难题。现在的Win8最佳效果可以明显强于Win7下的速度,也就是可以流畅玩爽红警2了。

    还有一种方法想要让Win8顺利兼容红警2,可以通过如下设置:

    1、窗口化(建议,否则在游戏与桌面之间切换时可能会黑屏)。RA2右键-属性-在目标里的路径后面加上“(空格)-win”。

    3f9d2980935732f3c75a243df1eb342e.png

    2.缩减到16位色(必须)。

    3.在RA2目录下找到RA2.ini,尤里复仇为RA2MD.ini,打开找到[Video],添加:VideoBackBuffer=no(必须)AllowHiResModes=yes(建议)AllowVRAMSidebar=no(建议)。

    4.微软KB2756872(必须),这个会装Win8的肯定会自己搜。做完以上步骤,红警2就可以完美运行了。

    上面4个步骤解决了红警2在Win8下兼容性不够的问题,操作完成之后再重新上红警2游戏,你会发现之前玩红警2会卡的问题完全消失,整个游戏画面无卡屏,非常流畅,更多精彩内容欢迎继续关注系统城www.xitongcheng.com!

    展开全文
    weixin_39885067 2021-08-07 10:01:30
  • 0、简单工厂模式 ( 不属于 GOF 设计模式中 ) 1、工厂方法模式 2、抽象工厂模式 3、建造者模式 4、单例模式 5、原型模式 三、结构型模式 1、适配器模式 2、装饰者模式 3、代理模式 4、外观模式 5、桥接模式 6、组合...





    一、七大设计原则




    1、开闭原则


    【设计模式】软件设计七大原则 ( 开闭原则 )


    开闭原则 :

    • 定义 : 一个 软件实体 , 类 / 模块 / 函数 , 对 扩展 开放 , 对 修改 关闭 ;
    • 抽象与实现 :抽象 构建框架 , 用 实现 扩展细节 ;
    • 优点 : 提高 软件系统 的 可复用性 及 可维护性 ;

    开闭原则 是 面向对象 设计 中 , 最基础的 设计原则 , 它指导我们建立稳定灵活的系统 ;

    开发新功能时 , 尽量 不修改原有的代码 , 尽量 使用扩展增加新功能 ;


    实现 开闭原则 的核心思想 是面向抽象编程 , 不是面向实现编程 ;

    定义的 对象类型 抽象类类型 或 接口类型 , 调用的方法抽象类 或 接口 中的方法 ;


    抽象是 稳定的 , 让类依赖于抽象 , 对于修改来说就是封闭的 ;

    通过 面向对象 的 继承 , 以及 多态机制 , 可以实现 对 抽象 的 继承 , 通过 重写改变其固有方法 , 或 实现新的扩展方法 ;


    2、依赖倒置原则


    【设计模式】软件设计七大原则 ( 依赖倒置原则 | 代码示例 )


    依赖倒置原则 : 高层模块 不应该 依赖 低层模块 , 二者都应该 依赖其抽象 ;
    抽象 不应该 依赖细节 , 细节应该依赖抽象 ;

    针对接口编程 , 不要针对实现编程 ;


    通过抽象 , 包括使用 接口 或 抽象类 , 使个各类或模块之间 彼此独立 , 互不影响 , 从而实现模块之间的 松耦合 , 降低模块间的耦合性 ;

    使用依赖倒置原则时的注意点 :

    • 每个类都 尽量 实现自接口继承抽象类 ;
    • 尽量 避免从具体的类派生 ;
    • 尽量 不要覆盖基类方法 ;

    依赖倒置原则的优点 : 减少类之间的 耦合性 , 提高系统 稳定性 , 提高 代码可读性 , 可维护性 , 可 降低修改程序所造成的风险 ;


    3、单一职责原则


    【设计模式】软件设计七大原则 ( 单一职责原则 | 代码示例 )


    单一职责原则 : 不要存在 多余一个 导致 类变更的原因 ;

    假设有一个类 , 负责 2 2 2 个职责 , 职责 1 1 1 和 职责 2 2 2 ;
    一旦 需求发生变更 , 如 职责 1 1 1 相关功能发生改变 ;
    修改该类的 职责 1 1 1 功能时 , 有可能导致原本运行正常的职责 2 2 2 发生故障 ;

    对于上述类 , 应该 分别针对 职责 1 1 1 和 职责 2 2 2 , 各自建立一个独立的类 , 这样就保证了系统的稳定性 ;
    这样修改 职责 1 1 1 和 职责 2 2 2 中的任何一个功能 , 都不会影响另外一个职责的功能 ;


    推荐的开发方法 : 使一个 类 / 接口 / 方法 只负责一项职责 ;


    单一职责优点 : 提高 类的 可读性 , 提高 系统的 可维护性 , 降低 类的复杂度 , 降低 变更引起的风险 ;

    类越简单 , 可读性越好 , 同时提高了可维护性 ;
    一个类只负责一个职责 , 比负责多个职责 , 类要 简单得多 ;
    变更是必然的 , 必须要接收变更 , 如果 单一职责原则遵守的好 , 当修改一个功能时 , 可以 显著降低对其它功能的影响 ;


    单一职责原则 不只是 面向对象 设计中特有的职责 , 只要是模块化的系统 , 都适合使用单一职责原则 ;


    4、接口隔离原则


    【设计模式】软件设计七大原则 ( 接口隔离原则 | 代码示例 )


    接口隔离原则 :多个 专门的 接口 , 不使用 单一 的总接口 , 客户端 不应该依赖不需要的 接口 ;

    一个类 对 另一个类 的依赖 , 应该建立在 最小接口 上 ; 如果 有一个 大接口 , 里面有 很多方法 , 如果使用一个类 实现该接口 , 所有的类都要实现 ;

    建立 功能 单一接口 , 不要建立 庞大 臃肿 的接口 ;

    尽量细化接口 , 接口中的方法尽量少 ;


    接口设计适度原则 : 接口隔离原则 中 最重要的就是 注意 适度原则 , 一定要适度 ;

    接口设计的 过大 , 过小 , 都不合适 ; 设计接口时 , 多花时间去思考策划 ;

    接口方法 尽量少 , 但要有限度 , 对接口进行细化 , 肯定能 提高系统设计的灵活性 , 但是如果 接口设计的过小 , 方法过少 , 则会 造成接口数量过多 , 提高整个程序设计的复杂性 ;


    接口隔离原则 优点 : 符合 高内聚 , 低耦合设计思想 , 使得类具有很好的 可读性 , 可扩展性 , 可维护性 ;

    • 降低耦合 : 平时设计接口时 , 只暴露客户端需要的方法 , 客户端不需要的方法 , 直接隐藏起来 ; 只有专注的为一个模块提供定制服务 , 才能 建立最小的依赖关系 , 这样就降低了耦合程度 ;

    • 提高内聚 : 减少对外交互 , 使用接口中最少的方法 , 完成最多的事情 ;

    实际开发中 , 实践接口隔离原则时 , 也要根据业务场景 , 业务模型 , 以及以后有可能会发生变更的地方 , 对于这些做一些预判 , 抽象出业务模型很重要 ;


    5、迪米特原则


    【设计模式】软件设计七大原则 ( 迪米特原则 | 代码示例 )


    迪米特原则 又称为 迪米特法则 , 最少知道原则 , 最少知识原则 ;

    定义 : 一个对象 应该 对 其它对象 , 保持最少的了解 ;

    尽量 降低 类之间的耦合 ;

    对外部引入的类 , 越少越好 ;

    迪米特原则优点 : 降低了 类 之间的耦合 ;


    代码实现的层面最佳做法 : 尽量不要对外公开太多的 public 方法 , 和 public 变量 , 尽量内敛 , 多使用 private / protected 权限 ;


    迪米特原则 的 核心观念 : 就是 类 之间的解耦 , 解耦 是有一定程度的 , 尽量做到 弱耦合 , 耦合程度越低 , 类的复用率 才能提高 ;

    由于 减少了 类之间 不必要的依赖 , 从而达到了 降低了 耦合 的目的 ;


    适度使用 : 使用 迪米特原则 要 适度 , 如果 过分使用迪米特原则 , 会 产生大量中介类 , 导致 系统变复杂 , 增加维护难度 ;

    使用 迪米特原则 要权衡利弊 , 既要做到 结构清晰 , 又要做到 低耦合 高内聚 ;


    如果存在一个方法 , 既可以放在 A 类中 , 又可以放在 B 类中 ;
    这种情况下 , 如果一个方法 可以放在本类中 , 既 不增加类间关系 , 也 没有对本类产生负面影响 , 就可以放到本类中 ;

    方法中 , 如果 参数 , 返回值 , 使用了其它类 , 导致了 import 导入了一个依赖 , 这就 增加了类间的关系 ;


    迪米特原则 强调 只和朋友交流 , 不和陌生人说话 ;

    这里的朋友 指的是 : 出现在 成员变量 , 方法的 输入 , 输出参数 中的类 , 称为成员朋友类 , 出现在方法体内部的类 , 不属于朋友类 ;

    也就是说 类 A , 我使用了 类 A 中的方法 , 或成员 , 尽量避免导致本类 import 导入新的类 ;


    6、里氏替换原则


    【设计模式】软件设计七大原则 ( 里氏替换原则 | 定义 | 定义扩展 | 引申 | 意义 | 优点 )
    【设计模式】软件设计七大原则 ( 里氏替换原则 | 代码示例 | 类示例 | 方法入参示例 | 方法返回值示例 )


    里氏替换原则定义 :

    如果 对每一个 类型为 T1对象 o1 , 都有 类型为 T2对象 o2 ,

    使得 以 T1 定义的 所有程序 P所有对象 o1替换成 o2 时 ,

    程序 P行为 没有发生变化 ,

    那么 类型 T2类型 T1子类型 ;


    符号缩写说明 : T 是 类型 Type , o 是 对象 Object , P 是 程序 Program ;


    通俗理解 :

    T1 类 生成 o1 对象 ,

    T2 类 生成 o2 对象 ,

    开发的 程序 P使用了 T1 类型 , 使用 T1 创建了对象 o1 ,

    将程序中 所有的 o1 都替换成 T2 o2 时 ,

    程序 P 的行为 , 没有发生变化 ,

    可以认为 T2 是 T1 的子类型 ;


    T2 是 T1 的子类型 , T1 则是 T2 的父类 ;

    里氏替换原则 是 继承复用 的基石 , 只有当 子类 可以 替换 父类 , 并且 软件功能不受影响 时 , 父类才能真正的被复用 , 子类也能在父类的基础上 增加新的行为 ;

    里氏替换原则 是对 开闭原则 的补充 , 实现开闭原则的关键是 进行抽象 , 父类 和 子类 的继承关系 , 就是 抽象 的具体实现 ;



    里氏替换原则引申意义 :

    子类 可以 扩展 父类的功能 , 但是绝对不能 改变 父类原有的功能 ;


    子类 可以 实现 父类的 抽象方法 , 但是 不能 覆盖 父类的 非抽象方法 ;


    子类中 可以 增加 自己特有的方法 ;


    重载 ( 输入参数 宽松 ) : 子类的方法 重载 父类的方法 时 , 方法的前置条件 ( 输入参数 ) , 要比 父类方法的输入参数更宽松 ;

    如 : 父类的参数是 HashMap , 如果要符合 里氏替换原则 , 子类如果重载父类方法 , 那么需要使用 Map 类型参数 ;
    ( 这里注意区分 重写 与 重载 , 重写是重写父类方法 , 重载是函数名相同 , 参数不同 )


    重写 ( 返回值 严格 ) : 当 子类的方法 重写 / 重载 / 实现 父类的方法时 , 方法的 后置条件 ( 返回值 ) 比父类更严格或相等 ;

    如 : 父类的返回值是 Map , 子类的相同的方法 是 Map 或 HashMap ;


    7、合成复用原则


    【设计模式】软件设计七大原则 ( 合成复用原则 | 代码示例 )


    合成复用原则 又称为 组合复用原则 , 合成/聚合复用原则 , 组合/聚合复用原则 ;


    合成复用原则定义 : 想要达到 软件复用 的目的 , 尽量使用 对象 组合/聚合 , 而不是 继承关系 ;


    聚合 是 has-A 关系 ; ( 关系较弱 ) 代表部分事物的对象 ( 次 )代表聚合事物的对象 ( 主 ) 生命周期无关 , 删除了聚合对象 , 不代表删除了代表部分事物的对象 ;

    组合 是 contains-A 关系 ; ( 关系较强 ) 一旦删除 代表组合事物的对象 ( 主 ) , 那么 代表部分事物的对象 ( 次 ) 也一起被删除 ;

    继承 是 is-A 关系 ;


    电脑 与 U 盘 是聚合关系 , 电脑没了 , U 盘可以独立存在 , 还可以接在其它电脑上 ;


    A 类中包含了 B 类的引用 , 当 A 类对象销毁时 , B 类引用所指向的对象也一同消失 , 没有任何一个引用指向他 , 该引用成为了垃圾对象 , 被回收 ; 这种情况就是 组合 ;

    加入 A 类销毁后 , B 类对象还有在其它位置被引用 , B 类对象不会被销毁 , 此时这种关系就是 聚合 ;





    二、创建型模式



    工厂方法模式 , 抽象工厂模式 , 建造者模式 , 单例模式 , 使用频率较高 ;

    原型模式 使用频率较低 ;


    0、简单工厂模式 ( 不属于 GOF 设计模式中 )


    【设计模式】简单工厂模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    简单工厂模式 : 由 一个 工厂对象 决定 创建出 哪一种 产品类 实例 ;

    简单工厂模式类型 : 创建型 ;


    简单工厂模式适用场景 :

    • 创建对象少 : 工厂类 负责 创建的对象 比较少 ;

    • 不关心创建过程 : 客户端 只知道 传入 工厂类 的参数 , 对于 如何创建对象 不关心 ;


    简单工厂模式优点 : 只需要传入 正确的参数 , 就可以 获取需要的对象 , 无需知道创建细节 ;
    工厂类中有必要的 判断逻辑 , 可以决定 根据当前的参数 创建对应的产品实例 , 客户端可以免除直接创建产品对象的责任 ;
    通过该模式 , 实现了对 创建实例使用实例责任分割 ;
    提供专门的 工厂类 用于创建对象 , 客户端 无需知道所创建的产品类的类名 , 只需要知道对应产品类的参数即可创建对象实例 ;


    简单工厂模式缺点 : 工厂类 职责 过重 , 如果要增加新的产品 , 需要 修改工厂类的判断逻辑 , 违背 " 开闭原则 " ;
    7 7 7 大设计原则 , 不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;


    1、工厂方法模式


    【设计模式】工厂方法模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    工厂方法模式 : 定义一个 创建对象接口 , 让 实现这个接口的子类 决定 实例化哪个类 , 工厂方法让 类的实例化 推迟到子类中进行 ;

    工厂方法模式类型 : 创建型 ;


    创建 实例对象 过程可能会很复杂 , 有可能会 导致大量的重复代码 , 工厂方法模式 通过 定义 一个单独的 创建 实例对象 的方法 , 解决上述问题 ;
    通过 子类 实现 这个方法 , 创建具体的 实例对象 ;


    工厂方法模式适用场景 :

    • 重复代码 : 创建对象 需要使用 大量重复的代码 ;
    • 不关心创建过程 : 客户端 不依赖 产品类 , 不关心 实例 如何被创建 , 实现等细节 ;
    • 创建对象 : 一个类 通过其 子类指定 创建哪个对象 ;

    客户端 不需要知道 具体 产品类 的 类名 , 只需要知道 所对应的工厂 即可 , 具体的产品对象 , 由对应的工厂创建 , 客户端只需要知道 产品 对应的 工厂 ;
    工厂方法模式 利用了 面向对象 的 多态性 , 和 里式替换 原则 ;
    子类对象 覆盖 父类对象 , 使 系统 更容易扩展 , 将 创建对象的过程 推迟到子类实现 , 创建对象的任务 , 委托给 多个 工厂子类 中的某一个 , 客户端不需要关心是哪个 工厂子类 创建的 产品对象 ;
    工厂子类 一般都是 需要的时候 , 动态指定 ;


    工厂方法模式优点 :

    • 不关心创建细节 : 用户 只需要 关心 所需产品 对应的工厂 , 无需关心创建细节 ;
    • 符合开闭原则 : 加入 新产品 , 符合开闭原则 , 提高可扩展性 ;

    工厂方法模式 中 , 使用 工厂类创建 产品对象 , 同时 隐藏了 具体的 产品类 被 实例化 的细节 ;


    工厂方法模式缺点 :

    • 增加复杂性 : 类的个数容易过多 , 增加系统复杂度 ;
      在 添加新产品 时 , 除了编写 新的产品类 之外 , 还要 编写该产品类对应的 工厂类 ;
    • 增加难度 : 增加了系统 抽象性理解难度 ;
      工厂方法本身 利用了抽象 , 该模式中会 引入抽象层 , 如果要动态创建产品类 , 还要 引入反射技术 ;

    设计模式 的 使用 , 要根据 实际的 业务场景 , 模型 综合平衡考量 , 不能过分遵守设计原则 和 设计模式 ;


    2、抽象工厂模式


    【设计模式】抽象工厂模式 ( 简介 | 适用场景 | 优缺点 | 产品等级结构和产品族 | 代码示例 )


    抽象工厂模式 : 提供 一个 创建 一系列 相关 或 相互依赖 对象 的接口 ;

    创建目标对象时 , 只需要直到对象的抽象类型或接口类型即可 , 不需要知道具体的实现类型 ;

    抽象工厂模式类型 : 创建型 ;


    抽象工厂模式 可以将 一组具有同一主题 , 单独的工厂 封装起来 ;
    在使用时 , 客户端 创建 抽象工厂 的实现 , 使用 抽象工厂 作为接口 , 来创建这一主题的对象 ;
    使用的时候 , 不需要知道 从内部 工厂方法中获得的 对象 的具体类型 ;
    客户端 只 使用这些对象的 通用接口 ;

    抽象工厂模式 实现了 一组对象的 实现细节使用 分离 ;


    抽象工厂模式适用场景 :

    • 忽略创建细节 : 客户端 不关心 产品实例 如何 被创建 , 实现等细节 ;
    • 创建产品族 : 强调 一系列 相关的 产品对象 , 一般是 同一个产品族 , 一起使用 创建对象需要大量重复的代码 ;
    • 产品类库 : 提供 一个 产品类 的库 , 所有的产品 以 同样的接口出现 , 使 客户端不依赖于具体实现 ;

    使用抽象工厂模式 , 可以在工厂变化时 , 不需要修改 客户端 使用工厂的 代码 ;


    抽象工厂模式优点 :

    • 隔离产品代码 :应用层 隔离 具体产品的代码 , 客户端 无须关心 产品创建 的细节 ;
    • 创建产品族 :一个系列 的 产品族 , 统一到一起创建 ;

    抽象工厂模式缺点 :

    • 扩展困难 : 规定了 所有 可能 被创建 产品集合 , 产品族扩展 新的产品 困难 , 需要 修改抽象工厂的接口 ;
    • 增加难度 : 增加了系统的 抽象性理解难度 ;


    产品等级结构和产品族 :

    下图中 , 有 椭圆形 , 圆形 , 正方形 , 三种产品 ;

    • 产品族 : 相同颜色的代表一个产品族 ;
    • 产品等级结构 : 相同形状的代表同一个产品等级结构 ;

    在这里插入图片描述

    如 : 方型 - 洗衣机 , 圆形 - 空调 , 椭圆 - 冰箱 ;

    • 横向 看 产品族 : 某 品牌下 有 方型 - 洗衣机 , 圆形 - 空调 , 椭圆 - 冰箱 , 这是一个产品族 ;
    • 纵向看产品等级结构 : 椭圆 - 冰箱 , 纵向指的是不同品牌的冰箱 ;

    工厂方法模式 针对的是 产品等级结构 , 可以 扩展多个相同的产品 ;

    抽象工厂模式 针对的是 产品族 , 在 某个品牌 的工厂中取出 洗衣机 类 , 取出的肯定是 该品牌的洗衣机实例对象 ;

    只要指出 产品所在的 产品族 以及 产品所在的 产品等级结构 , 就可以唯一确定一个产品 ;


    左侧的 纵坐标轴 上的 工厂 是 具体的工厂 , 从该具体的工厂中 创建的实例该产品族中的实例 ;

    在这里插入图片描述


    使用 工厂模式 还是 抽象工厂模式 , 要看具体的业务场景 ;
    当一个工厂 可以 创建 分属于 不同 产品等级结构一个 产品族 中的 不同对象时 , 使用 抽象工厂模式 ;

    如 :

    • 工厂 中可以创建 相同品牌的 洗衣机 , 冰箱 , 空调 等产品 , 使用 抽象工厂模式 ;
    • 如果 工厂中创建 不同品牌的空调 , 则使用 工厂方法模式 ;

    3、建造者模式


    【设计模式】建造者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    建造者模式 :一个复杂对象构建过程 与其 表示 分离 , 使得 同样的构建过程 , 可以 创建不同的表示 ;

    用户只需要 指定 需要建造的类型 就可以 得到该类型对应的产品实例 , 不关心建造过程细节 ;

    建造者模式就是 如何逐步构建包含多个组件的对象 , 相同的构建过程 , 可以创建不同的产品 ,


    建造者模式类型 : 创建型 ;


    建造者模式适用场景 :

    • 结构复杂 : 对象 有 非常复杂的内部结构 , 有很多属性 ;
    • 分离创建和使用 : 想把 复杂对象 的 创建使用 分离 ;

    当创造一个对象 需要很多步骤时 , 适合使用建造者模式 ;
    当创造一个对象 只需要一个简单的方法就可以完成 , 适合使用工厂模式 ;


    建造者模式优点 :

    • 封装性好 : 创建使用 分离 ;
    • 扩展性好 : 建造类之间 相互独立 , 在 一定程度上解耦 ;

    建造者模式缺点 :

    • 增加类数量 : 产生多余的 Builder 对象 ;
    • 内部修改困难 : 如果 产品内部发生变化 , 建造者也要相应修改 ;

    建造者模式 与 工厂模式 :

    • 注重点不同 : 建造者模式 更注重于 方法的调用顺序 ; 工厂模式 注重于 创建产品 , 不关心方法调用的顺序 ;
    • 创建对象力度不同 : 创建对象的力度不同 , 建造者模式可以创建复杂的产品 , 由各种复杂的部件组成 , 工厂模式创建出来的都是相同的实例对象 ,

    4、单例模式



    5、原型模式


    【设计模式】原型模式 ( 概念简介 | 使用场景 | 优缺点 | 基本用法 )
    【设计模式】原型模式 ( 浅拷贝 | 深拷贝 | 原型与单例冲突 | 禁用 final )


    原型模式 : 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象


    ① 设计模式类型 : 创建型设计模式 ;

    ② 原型实例对象 : 给出原型实例对象 , 根据该对象创建新对象 ;

    ③ 创建对象类型 : 创建对象的种类由原型的实例对象类型确定 ;

    ④ 创建方式 : 不调用构造函数 , 而是通过克隆原型的实例对象 , 使用现有对象创建另一个相同类型的对象 , 隐藏创建细节 ;


    原型模式使用场景 : 原型模式的目的是 降低实例对象个数 , 减少构造函数的调用次数 ;


    ① 类初始化消耗资源过多 : 如果类初始化时消耗过多的资源 , 如这个类中某个成员占用大量内存 , 为了节省开销 ;

    ② 初始化繁琐耗时 : 类对象创建时经过大量的计算 , 或与本地资源 ( 数据库 , 文件 ) 频繁交互 , 每次创建消耗大量的 CPU 与 时间资源 ;

    ③ 构造函数复杂 : 类中定义的构造函数复杂 ;

    ④ 实例对象数量庞大 : 如果在内存中循环创建了很多该实例对象 , 就可以使用原型模式复用不用的对象 , 用于创建新对象 ;


    1 . 原型模式优点 : 性能高 , 简单 ;


    ① 性能高 : 使用原型模式复用的方式创建实例对象 , 比使用构造函数重新创建对象性能要高 ; ( 针对类实例对象开销大的情况 )

    ② 流程简单 : 原型模式可以简化创建的过程 , 可以直接修改现有的对象实例的值 , 达到复用的目的 ; ( 针对构造函数繁琐的情况 )


    2 . 原型模式缺点 : 实现复杂 , 坑多 ;


    ① 覆盖 clone 方法 ( 必须 ) : 必须重写对象的 clone 方法 , Java 中提供了 cloneable 标识该对象可以被拷贝 , 但是必须覆盖 Object 的 clone 方法才能被拷贝 ;

    ② 深拷贝 与 浅拷贝 风险 : 克隆对象时进行的一些修改 , 容易出错 ; 需要灵活运用深拷贝与浅拷贝操作 ;





    三、结构型模式



    适配器模式 , 装饰者模式 , 代理模式 , 外观模式 , 桥接模式 , 享元模式 使用较频繁 ;

    组合模式不经常使用 ;


    1、适配器模式


    【设计模式】适配器模式 ( 概念 | 适用场景 | 优缺点 | 外观模式对比 | 适配器模式相关角色 | 类适配器 | 对象适配器 | 实现流程 )


    适配器模式 :


    ① 设计模式类型 : 结构型 ;

    ② 概念 : 将 类的接口 转换成用户可以调用的 另外一个接口 ;

    ③ 目的 : 使接口不兼容的两个类可以一起工作 ;

    ④ 概念中的三个角色 : 被适配者 ( 现有的功能类 ) , 用户目标接口 ( 用户调用的接口 ) , 适配器类 ( 用户通过调用该类 , 间接调用 被适配者类 ) ;

    ⑤ 简易原理 : 适配器类 实现用户目标接口 , 在该接口的实现类中调用被适配者 , 实现了接口转接的效果 ; 使用的时候 , 通过创建适配器类 , 即可间接调用被适配者方法 ;


    适配器模式 适用场景 :

    1 . 功能正确但接口不匹配 : 对于之前开发好的类 , 该类的操作和返回值都是正确的 , 但是其定义的方法接口无法调用 , 此时使用适配器模式 , 使该类与用户的接口匹配 , 让用户使用适配器的接口 , 间接调用该类 ;


    2 . 适配器模式使用阶段 : 软件设计开发阶段一般不使用适配器模式 , 在软件维护时 , 出现操作和返回值类似 , 但是函数接口不同 , 为了适配第三方系统的接口 , 使用适配器模式 ;


    设计阶段不要使用适配器模式 ;


    3 . 适配器的两种实现方式 : 对象适配器模式 与 类适配器 ;


    ① 对象适配器 : 符合组合复用原则 , 使用了委托机制 ; ( 通过组合实现 , 适配器类中维护被适配者成员 )

    ② 类适配器 : 通过类的继承实现适配器模式 ; ( 通过继承实现 , 适配器类继承被适配者类 )


    推荐使用对象适配器模式 , 在继承与组合二者之间 , 优先选择组合方案 ;


    1 . 适配器模式 优点 :


    ① 复用且不修改类 : 不改变现有类的基础上 , 提高类的复用性 , 透明性 ; 让现有类与目标类接口匹配 ;

    ② 降低耦合 : 目标类 ( 用户调用的接口所在类 ) 和 现有类 ( 被适配者 ) 解除耦合 , 降低了系统的耦合性 , 易于扩展维护 ;

    ③ 符合开闭原则 : 用户调用适配器接口 , 只与适配器类进行交互 , 如果需要修改扩展 , 只需要修改适配器类即可 , 目标类 和 现有类 各自都是相互独立的 , 互不影响 ;


    2 . 适配器模式 优点 :


    ① 增加复杂性 : 编写适配器类时 , 需要考虑全面 , 包括被适配者 和 目标类 , 系统复杂性会增加 ;

    ② 降低可读性 : 系统代码可读性降低 , 可维护性降低 ;


    阅读代码时 , 调用系统接口 , 如果调用的是适配器接口 , 还要到处找调用的是哪个现有类的实际接口 ;



    适配器模式 与 外观模式对比 :

    1 . 相同点 : 都是对现有类进行封装 ;


    2 . 行为分析 :


    ① 外观模式行为 : 外观模式定义了新街口 , 处理多个子系统之间的交互 ;

    ② 适配器模式行为 : 适配器模式复用原有接口 , 只是简单的转接了一下 , 使两个现存接口 ( 现有类 和 目标类 ) 协同工作 ;


    3 . 适配力度分析 :


    ① 外观模式 : 适配力度很大 , 需要开发整个子系统之间的交互流程 ;

    ② 适配器模式 : 修改很少的内容 , 只是进行简单的接口转接交互 , 一般不实现具体的功能 ;


    适配器模式 相关角色 ( 重点 )

    1 . 被适配者 : 实际功能提供者 , 是系统中原有的类 ;


    2 . 用户目标接口 : 用户调用该接口 , 实现功能操作 ; 是适配器的父类接口 ;


    3 . 适配器 : 需要实现 用户目标接口 , 并在接口中的操作中 , 调用被适配者提供的实际功能 ; 适配器有两种途径实现 , 分别是类适配器 , 对象适配器 ;


    ① 类适配器 : 继承被适配者 , 通过 super 访问被适配者方法 ;

    ② 对象适配器 ( 推荐 ) : 在适配器中维护一个被适配者成员变量 , 通过成员变量访问被适配者方法 ;


    适配器模式 ( 对象适配器 ) 代码实现流程 ( 重点 )

    1 . 明确被适配者 : 被适配者 是一个现有类 , 该类保持不变 ;


    2 . 定义用户目标接口 : 用户通过调用该接口 , 实现实际的功能 , 该功能与适配者中的功能类似 , 但 接口不同 ;


    3 . 声明适配器 :


    ① 适配器 实现 用户目标接口 : 适配器 需要实现 用户目标接口 , 在实现的接口方法中 , 需要将实际操作 委托给 被适配者 ;

    ② 适配器 维护 被适配者 类型成员变量 : 如何调用到 被适配者 的方法呢 , 这里 适配器 通过 定义 被适配者 类型的成员变量 , 通过该 被适配者 类型成员变量 , 调用 被适配者 public 方法 ;

    ③ 委托操作 : 在实现的 用户目标接口中 , 通过 被适配者类型 成员变量 , 调用 被适配者 的方法实现具体功能 ;


    类适配器 对象适配器 , 本质区别就是 适配器类访问 被适配者的途径 ;
    类适配器 : 通过继承 被适配器 , 获取访问被适配器方法的资格 ;
    对象适配器 : 通过在其内部维护一个 被适配者 成员变量 , 进而通过该成员变量访问 被适配者方法 ;


    4 . 用户访问操作 :


    ① 定义目标接口变量 : 定义 用户目标接口 对象变量 ;

    ② 目标接口变量赋值 : 创建 适配器对象 赋值给上述 用户目标接口对象变量 , ( 适配器 是 用户目标接口 的子类 ) ;

    ③ 目标接口调用 : 调用用户目标接口 , 即可调用被适配者的实际功能方法 ;


    2、装饰者模式


    【设计模式】装饰者模式 ( 概念 | 适用场景 | 优缺点 | 与继承对比 | 定义流程 | 运行机制 | 案例分析 )


    装饰者模式概念 :


    ① 设计模式类型 : 结构性 ;

    ② 概念 : 不改变原有类的对象 , 动态地将额外的功能附加到该对象上 ;

    ③ 扩展对象功能 : 这种功能扩展方式比类继承更加灵活 ;

    ④ 装饰者模式 : 移除类中的被装饰功能 , 将被装饰类简化 , 区分类的核心职责 和 装饰功能 ;


    装饰者模式适用场景 :


    ① 功能扩展 : 为一个类扩展功能 , 为其添加额外的职责 ; ( 强调扩展 )

    ② 动态添加撤销功能 : 为一个对象动态添加额外功能 , 同时这些被添加的功能还能被动态撤销 ; ( 强调动态 )


    装饰者模式优点 :


    ① 扩展灵活 : 使用装饰者模式 , 比继承更加灵活 ; 使用装饰者模式扩展类功能 , 不会改变原来的类 ;

    ② 排列组合 : 对装饰类进行各种排列组合 , 可实现不同的扩展功能 ;

    ③ 开闭原则 : 装饰者模式符合开闭原则 , 被装饰的类 , 和装饰类相互独立 , 互不干扰 ;


    装饰者模式缺点 :


    ① 程序复杂 : 需要编写更多的代码 , 生成更多的类 , 程序的复杂性增加了 ;

    ② 动态 / 多层 装饰 : 动态 / 多层 装饰一个类时 , 程序更复杂 ;


    3、代理模式


    【设计模式】代理模式 ( 简介 | 适用场景 | 优缺点 | 代理扩展 | 相关设计模式 )
    【设计模式】代理模式 ( 静态代理 )
    【设计模式】代理模式 ( 动态代理 )


    代理模式 :其它对象 提供 一种代理 , 以 控制这个对象 的访问 ;

    代理对象客户端目标对象 之间 起到 中介的作用 ;


    如 : 租客通过中介找房东租房子 , 房东将房子托管给了中介 , 房东是目标对象 , 但是租赁行为是中介来执行的 , 中介是代理类 , 租客 就是 客户端 ;
    中介 代理 房东 进行租赁行为 , 相当于 代理类对目标对象进行了增强 ;

    客户端 通过 代理类 与 目标对象 进行交互 , 客户端 不直接接触 目标对象 ;


    代理模式类型 : 结构性 ;


    代理模式适用场景 :

    • 保护目标对象 : 客户端 只与 代理类 进行交互 , 不清楚 目标对象 的具体细节 ; 相当于 租客 只与 中介 进行交互 , 不知道房东的信息 ;
    • 增强目标对象 : 代理类 在 目标对象的基础上 , 对 目标对象的功能 进行增强 ;

    代理模式优点 :

    • 分离目标对象 : 代理模式 能将 代理对象 与 真实被调用的 目标对象 分离 ;
    • 降低耦合 : 在一定程度上 , 降低了系统耦合性 , 扩展性好 ;
    • 保护目标对象 : 代理类 代理目标对象的业务逻辑 , 客户端 直接与 代理类 进行交互 , 客户端 与 实际的目标对象之间没有关联 ;
    • 增强目标对象 : 代理类 可以 在 目标对象基础上 , 添加新的功能 ;

    代理模式缺点 :

    • 类个数增加 : 代理模式 会 造成 系统中 类的个数 增加 , 比不使用代理模式增加了代理类 , 系统的复杂度增加 ; ( 所有的设计模式都有这个缺点 )
    • 性能降低 : 在 客户端 和 目标对象 之间 , 增加了一个代理对象 , 造成 请求处理速度变慢 ;

    静态代理 : 在代码中 , 使用指定的代理 ; 显示的定义了一个业务实现类代理 ; 在代理类中 , 对同名的业务方法进行包装 , 用户通过调用 代理类中 被包装过的业务逻辑方法 , 来调用 被包装对象 的业务方法 , 同时对目标对象的业务方法进行增强 ;


    动态代理 : 由 JDK 提供 , 只能对 实现的接口的类 进行动态代理 , 不能代理具体的实现类 ; 通过 接口 中的 方法名 , 在 动态生成的 代理类 中 , 调用 业务实现类同名方法 ;

    JDK 动态代理 , 用到的代理类 , 是在程序调 用到代理对象时 , 由 Java 虚拟机创建 , Java 虚拟机 根据传入的 业务实现类对象 以及 方法名 , 动态地创建代理类 Class 文件 , 当该 Class 文件被字节码引擎执行 , 通过该代理类对象进行目标方法的调用 ;

    动态代理无法代理类 , 只可以代理接口 ;


    CGLib 代理 : 可以 针对类实现进行代理 ;

    如果要 代理一个类 , CGLib 会生成一个被代理类的子类 , 通过 继承该类覆盖其中的方法 ;
    如果该类时 final 的 , 则无法被继承 , 如果类中的方法是 final 的 , 该方法无法被重写 ;
    使用 CGLib 代理要特别注意 final 修饰符 ;


    4、外观模式


    【设计模式】外观模式 ( 概念 | 适用场景 | 优缺点 | 代码示例 )


    1 . 外观模式概念 :


    ① 设计模式类型 : 结构型 ;

    ② 标准定义 : 提供一个统一接口 , 用于访问子系统中的一群接口 ;

    ③ 隐藏复杂性目的 : 定义高层级接口 , 让子系统更容易使用 , 目的是隐藏系统的复杂性 ;

    ④ 交互流程 : 多个子系统联合完成一个操作 , 提供一个统一的接口 , 供客户端调用 , 客户端不与每个子系统进行复杂的交互 , 客户端只与提供接口的外观类进行交互 ;


    2 . 外观模式的相关角色 :


    ① 外观角色 : 外观类有自己的方法 , 用户可以通过调用外观类的方法 , 调用子系统提供的功能 ;

    ② 子系统角色 : 可以是若干个处理模块 , 数量 1 个或多个 ;

    ③ 用户角色 : 用户通过外观类调用子系统的功能 ;


    外观模式适用场景 :

    ① 子系统复杂 : 子系统复杂 , 通过使用外观模式可以简化调用接口 ;

    ② 层次复杂 : 系统结构层次复杂 , 每个层级都一个使用外观对象作为该层入口 , 可以简化层次间的调用接口 ;


    5、桥接模式


    【设计模式】桥接模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    桥接模式 :

    • 分离抽象实现 :抽象部分 与 它的 具体实现部分 分离 , 使它们 都可以 独立的 变化 ; 独立的变化 就是 在一定程度上 进行解耦 ;
    • 组合方式 : 通过 组合 的方式 建立 两个类 之间的联系 , 而 不是 继承 ;

    桥接模式类型 : 结构型 ;


    桥接模式 相当于 使用桥梁 将两侧连接起来 , 这里指的是 使用桥梁 连接两个类 , 在两个类之间建立某种联系 , 可以通过继承 , 也可以通过组合 , 桥接模式 是采用 组合的方式 , 建立两个类之间的关系 ; 合成复用原则 , 推荐优先使用组合 , 不是继承 ; 桥接模式 可以防止子类过多 , 造成系统复杂的情况 ;


    桥接模式的重点 是 理解 类的 抽象部分 和 具体的实现部分 ;

    • 抽象过程 : 抽象部分 , 经过 抽象化 , 忽略某些信息 , 将不同的实体当做同一个对待 ; 面向对象中 , 将对象的共同性质抽取出来 , 形成类的过程 , 就是抽象化过程 ;

    • 实现过程 : 对于具体实现的部分 , 也要进行实现化 , 针对抽象化 , 给出具体实现 ; 这个过程就是实现过程 , 过程的产出就是具体实现部分 , 具体实现部分产生的对象 , 比抽象产生的更具体 , 是对抽象化事物的具体化产物 ;


    如 : 开发跨平台的视频播放器 , 平台有 Android , iOS , Windows , Linux , Mac , 播放器支持的格式有 MP4 , AVI , RMVB , FLV 格式 ; 这种情况下 , 适合使用桥接模式 ;


    桥接模式适用场景 :

    • 抽象实现灵活 : 抽象具体实现 之间 , 需要 增加更多灵活性 的情况下 , 适合使用桥接模式 ;
      使用 桥接模式 , 可以 避免在这两个层次之间 , 建立静态的继承关系 , 通过 桥接模式 在二者之间建立 关联关系 ;
      抽象 和 实现 都可以 各自 以继承的方式扩展 , 互不影响 ;
      可以动态的 将 抽象 的子类对象实现 的子类对象 进行组合 , 在系统中 , 抽象 和 实现 之间进行了解耦 ;

    • 独立变化维度 : 一个类存在 2 2 2 个或更多的 独立变化维度 , 并且这些维度都需要 独立扩展 ;
      抽象部分可以 独立扩展 , 具体实现的部分 , 也可以独立扩展 ;

    • 不使用继承 : 不希望使用继承 , 或 因多层继承导致系统类的个数增加 ;


    6、组合模式


    【设计模式】组合模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    组合模式 :对象 组合成 树形结构 , 表示 " 部分-整体 " 层次结构 ;

    组合模式 使 客户端单个对象组合对象 保持一致的 方式处理 ;


    如 : 文件系统 , 根目录下有若干文件和目录 , 在二级目录下还有目录和文件 , 这种情况下 , 适合使用组合模式 ;


    组合模式类型 : 结构型


    组合模式适用场景 :

    • 忽略差异 : 希望 客户端 可以 忽略 组合对象单个对象 的差异 ;
    • 处理树形结构 ;

    7、享元模式


    【设计模式】享元模式 简介 ( 定义 | 对象池 | 内部状态 | 外部状态 | 适用场景 | 相关角色 )
    【设计模式】享元模式 实现 ( 实现流程 | 抽象享元类 | 具体享元类 | 享元工厂 | 用户调用 | 代码模板 )


    1 . 享元模式 简介 : 享元模式的核心是 对象池 , 使用对象时 , 先从对象池中获取对象 , 如果对象池中没有 , 创建一个 , 放入对象池 , 然后再从对象池中获取 ; ( 只能从对象池中拿对象 , 不能自己创建 )


    ① 设计模式类型 : 结构性 ;

    ② 享元模式 概念 : 通过减少创建对象数量 , 改善应用中使用对象的结构 , 使用 共享对象 ( 对象池中的对象 ) , 支持多个 细粒度对象 ( 使用时的大量对象 ) ;

    ③ 好处 : 减少创建对象的数量 , 从而减少内存的占用 , 提高性能 ;


    2 . 细粒度对象 和 共享对象 : 目的是为了提高程序性能 ;


    ① 细粒度对象 : 是内存中的数量庞大的对象 ; 实际使用的数量庞大的对象 ;

    ② 共享对象 : 多个细粒度对象共享的部分数据 ; 对象缓存池中存储的对象 ;

    ③ 举例说明 : 使用字符串值 “abc” , 首次使用 , 创建该字符串 , 将其放入字符串缓存池中 , 这个缓存池中的字符串就是 "共享对象" , 应用中要大量使用 “abc” 字符串 , 比如使用 10 万个 “abc” 字符串对象 , 这 10 万个字符串对象就是 "细粒度对象" , 此时肯定不会创建这么多对象 , 这 10 万个对象使用时从字符串缓存池中查找缓存的那个 "共享对象" 即可 , 这样节省了很大的内存开销 ;


    3 . 享元模式示例 : Java 的 String 类型就是用了享元模式的设计模式 ;

    ① 定义字符串 : String str = "Hello" ;

    ② 内存中已有该字符串 : 如果之前已经有该字符串 , 就直接将字符串缓存池中的字符串返回 ,

    ③ 新字符串 : 如果内存中没有该字符串 , 就创建一个新的字符串 , 放入缓存池中 ;


    享元模式就是池技术 , 如字符串池 , 数据库连接池 等 ;
    使用对象时 , 先从池中查找 , 没有找到再创建该对象 , 然后放入对象池中 ;


    4 . 享元模式使用策略 : 用户想要调用一个对象 , 去对象池中查找 , 如果对象池中有该对象 , 那么直接使用该对象 , 如果没有 , 创建该对象 , 放入对象池中 , 然后再从对象池中获取该对象 ;

    对象对比 : 这里涉及到一个问题 , 如何确定对象池中的对象是不是用户想要使用的对象呢 ?


    5 . 引入 内部状态 和 外部状态 : 对象对比问题引出这两个概念 , 对象中有很多数据 , 那么使用什么数据来确定两个对象是否一致呢 , 这里使用 对象的 外部状态 来确定 ;


    ① 内部状态 : 对象的内部状态不能作为对象对比的依据 , 所有对象的内部状态都是一样的数据 ;

    ② 外部状态 : 对象的外部状态每个都不一样 , 每个对象都有一个唯一 外部状态 值 , 类似于 身份证 , 哈希码 这一类的信息 ;

    ③ 身份标识 : 在线程池中 , 使用外部状态信息 , 唯一确定一个对象 , 作为对象的标识信息 ;


    1 . 概念引入 : 区分这两个概念的目的是为了维护享元模式的对象池 , 当用户想要使用某个对象时 , 如何确定对象池中的对象是否是用户想要调用的对象呢 , 这里就需要一些数据进行对比 , 数据一致 , 就说明是用户想要的对象 , 数据不一致 , 就需要创建新对象 , 放入对象池 ;


    ① 内部状态 : 有些数据所有的对象都一样 , 显然不能当做对象一致性对比的依据 , 这就是 内部状态 ;

    ② 外部状态 : 有些数据每个对象都不一样 , 根据该数据确定对象的唯一性 , 相当于 哈希码 , 身份证号 , 档案编号 这一类的数据 , 这就是外部状态 ;


    内部状态 和 外部状态 本质是 信息数据


    2 . 内部状态 ( 共享信息 ) : 在享元模式中的对象中 , 不随环境改变而改变的信息 ;


    ① 共享信息 : 内部状态就是可以被共享的信息 ;

    ② 存储位置 : 该信息存储在享元对象内部 ;

    ③ 存储形式 : 该信息作为对象的附加数据 , 不在具体的对象中存储 , 可以被多个对象共享 ;


    3 . 外部状态 ( 不可共享信息 ) : 随着外部环境改变 , 对象内部跟着改变 , 这部分内容就不能进行共享 ;

    不可共享 : 外部状态不可被共享 , 每个值都必须在不同的对象中维护 ;


    1 . 享元模式 适用场景 :


    ① 底层开发 : 某个系统的底层开发 , 对性能要求比较高 , 可使用享元模式 ;

    ② 缓冲池 : 系统中实例对象数量庞大 , 需要缓冲池处理这些对象 ;


    2 . 享元模式使用前提 : 系统中存在大量的对象 , 这些对象状态大部分功能可以外部化 , 将这些功能抽离出来 , 只在内存中保留一份 ;


    ① 分离对象功能 : 系统中如果内存中持有大量对象 , 可能会溢出 , 将这些对象相同的部分分离出来 ;

    ② 用户调用行为 : 如果有相同的业务请求 , 则优先使用内存中已有的对象进行处理 , 避免使用大量相同的对象 ;

    ③ 注意 : 这里只有在内存中有大量相同对象时 , 才考虑享元模式 , 如果内存中某类型的对象数量较少 , 没有必要使用该模式 ;





    四、行为型模式




    1、策略模式


    【设计模式】策略模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    策略模式 : 定义了 算法家族 , 分别 封装起来 , 让它们之间 , 可以 相互替换 , 此模式 让 算法的变化 不会影响到 使用算法的用户 ;

    不同的算法 , 封装到 不同的类 中 , 让它们之间可以 相互替换 ,
    使用算法的用户 即 应用层 , 感知不到 算法已经被替换了 ;


    实际的业务场景 :

    • 不同业务逻辑 : 商品促销 , 促销策略 , 不同的促销策略算法 , 封装到不同的类中 ;
    • 代码优化 : 如果代码中 , 有大量的 if … else … 代码 , 可以通过策略模式 , 替换相关逻辑 ;

    策略模式类型 : 行为型 ;


    策略模式适用场景 :

    • 行为切换 : 系统有 很多类 , 这些类的区别仅仅在于它们的 行为不同 ; 使用策略模式 , 可以 动态地用户对象 在这些行为中, 选择一个行为 ;
      将对象的 不同的行为 , 封装到 不同的类 中 , 每个行为对应一种策略 ;

    • 算法选择 : 系统中需要 动态地几种算法选择一种 ;
      算法 就是 策略 , 其中封装了一系列的业务逻辑及计算方式 ;
      如 : 计算方式 , 给定两个数字 ; 使用加法策略 , 将两个数相加 ; 使用乘法策略 , 将两个数相乘 ;


    2、观察者模式


    【设计模式】观察者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    观察者模式 : 定义了 对象之间 一对多 的 依赖 , 令 多个 观察者 对象 同时 监听 某一个 主题对象 , 当 主题对象 发生改变时 , 所有的 观察者 都会 收到通知 并更新 ;

    观察者 有多个 , 被观察的 主题对象 只有一个 ;

    如 : 在购物网站 , 多个用户关注某商品后 , 当商品降价时 , 就会自动通知关注该商品的用户 ;

    • 主题对象 : 商品是主题对象 ;
    • 观察者 : 用户是观察者 ;
    • 观察者注册 : 用户关注 , 相当于注册观察者 ;
    • 通知触发条件 : 商品降价 ;

    观察者模式 类型 : 行为型 ;


    观察者模式适用场景 : 关联行为 场景 , 建立一套 触发机制 ;

    如 : 用户关注某个商品的价格 , 降价时进行通知 , 这样 用户商品 产生了关联 , 触发机制就是 商品降价 ,


    3、责任链模式


    【设计模式】责任链模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    责任链模式 , 又称为 职责链模式 ;

    责任链模式定义 :请求 创建一个接收该 请求对象 , 链条中每个元素都是一个对象 ;

    责任链模式类型 : 行为型 ;


    责任链模式 适用场景 : 一个 请求处理 , 需 要多个对象 中的 一个或若干个对象 协作进行处理 ;


    责任链模式 优点 :

    ① 解耦 : 请求的 发送者接收者 解耦 ; 接收者 是 请求的处理者 ;

    ② 动态组合 : 责任链 可以 动态组合 , 使用配置 设置责任链的 顺序是否出现 ; 可以随时对责任链排序 , 随时增加拆除责任链中的某个请求对象 ;


    责任链模式 缺点 :

    ① 性能 : 如果 责任链 太长 , 或责任链中请求的 处理时间过长 , 可能会 影响性能 ;

    ② 个数 : 责任链 可能过多 ;


    责任链模式 与 状态模式 比较 :

    责任链模式 中 , 并 不指定 下一个处理的 请求对象 是哪个 ; 责任链 中 链条顺序 可以 任意组合排序 ;

    状态模式 中 , 需要让 每个 状态 知道下一个需要处理的状态是谁 ;


    4、备忘录模式


    【设计模式】备忘录模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    备忘录模式 : 保存 对象 某个状态 , 以便在 适当的时候 恢复对象 ;
    ( 形象的比喻 : " 后悔药 " )

    如 : 游戏存档 , 一些编辑工具中的 " 撤销 " 操作 , 浏览器中的 后退 ;


    备忘录模式 类型 : 行为型 ;


    备忘录模式 适用场景 :

    • 撤销操作 : 保存 / 恢复 数据 的相关业务场景 ;
      如 : 在 Word 中编写文档 , 如果想要撤销之前的 输入 / 删除操作 , 使用 Ctrl + Z 执行 " 撤销 " 操作 ;

    • 状态恢复 : 在 " 后悔 " 的时候 , 将对象恢复到之前的状态 ;
      如 : 游戏中的存档使用 ;


    5、模板方法模式


    【设计模式】模板方法模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    模板方法模式 : 定义了一个 算法骨架 , 并允许 子类 为 一个或多个 步骤 提供实现 ;

    模板方法模式 可以使 子类 在不改变 算法结构 的前提下 , 重新定义算法的某些步骤 ;


    模板方法模式类型 : 行为型 ;


    模板方法模式适用场景 :

    • 父类视角 : 一次性 实现 一个算法 不变的部分 , 并将 可变部分 留给 子类 实现 ;
    • 子类视角 : 各个子类中 , 公共部分 被提取出来 , 集中到一个公共的父类中 , 避免代码重复 ;

    模板方法模式的目的是 让 子类可以扩展具体实现固定方法的某个具体的步骤 ; 对于模板来说 , 是一套固定的算法 , 通过子类 可以扩展 固定算法中某些算法步骤 ;


    6、迭代器模式


    【设计模式】迭代器模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    迭代器模式 : 提供一种方法 , 顺序访问 集合对象 中的 各个元素 , 而 不暴露 该对象 的内部表示 ;


    迭代器模式类型 : 行为型 ;


    迭代器模式适用场景 :

    • 内容保密 : 访问 集合对象 的内容 , 无需暴露内部表示 ;
    • 统一接口 : 为遍历 不同的 集合结构 , 提供统一接口 ;

    迭代器模式优点 : 分离 了 集合对象 的 遍历行为 ; 抽象出了 迭代器 负责 集合对象的遍历 , 可以让外部的代码 透明的 访问集合内部的数据 ;


    迭代器模式缺点 : 类的个数成对增加 ; 迭代器模式 , 将 存储数据 , 遍历数据 两个职责拆分 ; 如果新添加一个 集合类 , 需要增加该 集合类 对应的 迭代器类 , 类的个数成对增加 , 在一定程度上 , 增加了系统复杂性 ;


    7、中介者模式


    【设计模式】中介者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    中介者模式 : 定义 一个 封装一组对象 如何 交互对象 ;

    通过使 对象 明确地 相互引用 , 促进 松散耦合 , 允许 独立改变 它们之间的 交互 ;


    中介者模式类型 : 行为型 ;


    中介者模式适用场景 :

    • 引用关系复杂 : 系统中 对象之间 存在 复杂的 引用关系 , 产生的 相互依赖关系 结构混乱 , 难以理解 ;
    • 改变行为 : 交互的 公共行为 , 如果 需要 改变行为 , 可以 增加新的 中介者 类 ; ( 通过增加新的中介者类 , 达到扩展的目的 )

    多人聊天室 就是一个 中介者模式 场景 , 一个人发言时 , 需要传达给每个人 , 如果没有聊天室 , 需要对每个人都说一遍 , 如果有中介者 , 就由中介者负责将发言传达给每个人 ;


    中介者模式优点 :

    • 降低复杂度 :一对多 转化为 一对一 , 降低了 程序复杂程度 ;
      如 : 聊天室中有 8 8 8 个人 , 如果要一对一进行交互 , 需要交互 7 7 7 次 ; 使用了中介者模式后 , 变成一对一 , 只要将交互内容交给中介者就可以了 , 中介者负责与其余 7 7 7 人进行交互 ;

    • 解耦 : 实现了 类之间的解耦 操作 ;
      如 : 聊天室中有 8 8 8 个人 , 每个人都需要耦合另外 7 7 7 个 , 即持有另外 7 7 7 个对象 , 使用了中介者模式之后 , 8 8 8 个人只需要持有 中介者 对象即可 , 8 8 8 个人之间不再进行相互耦合 ;


    中介者模式缺点 : 如果在 业务场景 中 中介者 数量过多 , 会导致系统 复杂性增加 ;
    ( 设计模式之间 , 也是一个相互平衡的过程 )


    8、命令模式


    【设计模式】命令模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    命令模式 :不同的请求 封装成 不同的请求对象 , 以便 使用 不同的 请求 ; 对于接收者来说 , 可以识别 不同的 请求对象类型 , 然后执行 不同的操作 ;

    命令模式 , 解决了 应用程序 中 , 对象的职责 ( 发送请求 / 执行请求 ) , 以及它们之间的 通信方式 ;

    命令模式 可以使 命令的 发送者接收者 完全解耦 ; 发送者 和 接收者 之间 , 并没有直接的关系 , 二者靠 命令 进行交互 ;

    命令发送者 只需要知道发送 请求对象 , 不需要知道如何完成请求 ;

    命令执行者 只需要知道如何 完成请求 , 不需要知道请求的发送过程 ;


    命令模式类型 : 行为型 ;


    命令模式 适用场景 :

    • 解耦发送者与接收者 : 请求发送者请求接收者 ( 执行者 ) 需要 解耦 , 发送者 与 接收者 之间 不直接进行交互 ;
    • 抽象行为 : 需要将 等待执行 的行为 抽象出来 ;

    9、访问者模式


    【设计模式】访问者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    访问者模式 : 封装 作用于 某种 数据结构 的 各元素 操作 , 数据结构指的是 List , Set , Map 等 ;

    不改变 元素类 的前提下 , 定义 作用于 元素 的操作 ;


    访问者模式类型 : 行为型 ;


    访问者模式 适用场景 :

    • List , Set , Map 等 数据结构 中 , 包含 很多类型的对象 ;

    • 数据结构 与 数据操作 分离 ;


    10、解释器模式


    【设计模式】解释器模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    解释器模式 : 给定一个 语言 , 定义它的 文法 的一种表示 , 并定义一个 解释器 , 这个 解释器 使用该表示来 解释 语言中的 句子 ;

    文法 可以理解成一种 语法 ;

    为了解释一种语言 , 而为语言创建的 解释器 ;

    如 : Java 代码 , 需要编译器进行编译之后才能运行 , 这个编译器就相当于解释器 ;


    解释器模式类型 : 行为型 ;


    解释器模式适用场景 : 某个 特定类型问题 发生频率 足够高 ;

    日志处理 : 使用 脚本语言 或 编程语言 处理日志时 , 有很多服务 会产生 大量的日志 , 需要 对日志进行解析 , 生成报表 ;

    各个服务的日志格式不同 , 数据中的要素相同 , 这种情况下 , 通过程序解决上述问题 , 主要的解决方案就是使用解释器模式 ;


    日常项目中 , 解释器模式使用情况很少 ;

    解释器一般是 开源包 , 如 Express4J , JEP ;


    11、状态模式


    【设计模式】状态模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )


    状态模式 : 允许 对象 在 内部状态 改变时 , 改变它的行为 ;

    一个对象 , 如果其 内部状态改变 , 其 行为也需要进行改变 ; 如果其行为不需要改变 , 也可以只 控制 该对象的状态 的 互相转换 ;

    当控制一个对象 , 其状态转换过程比较复杂时 , 将 状态判断逻辑 , 转到代表不同状态的一系列类中 ;

    如 : 引入 视频播放 的业务场景 , 播放器有 初始状态 , 播放状态 , 暂停状态 , 停止状态 , 快进状态 等多种状态 , 将这些 状态 都封装到 代表不同状态的类 中 , 可以将复杂的判断逻辑简化 , 将这些 逻辑 扩展到不同的状态类中 ;


    状态模式类型 : 行为型 ;


    状态模式适用场景 : 一个对象 , 存在多个状态 , 状态可以相互转换 ; 不同状态下 , 行为不同 ;


    不同状态下 , 行为不同的示例 , 如 :

    购买物品 , 将物品放入购物车并生成订单 , 可以进行付款 ; 如果 订单 超过 24 小时后 , 被关闭订单 , 此时订单取消 , 无法付款 ;

    电梯运行时 , 不能开门 ; 电梯停止后 , 才能开门 ;

    展开全文
    han1202012 2021-08-23 00:04:38
  • Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。 5.1 前言 接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种...

    结构型模式

    结构型模式主要是用于处理类或者对象的组合,它描述了如何来类或者对象更好的组合起来,是从程序的结构上来解决模块之间的耦合问题。它主要包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式这个七个模式。

    五、适配器模式

    将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

    5.1 前言

    接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化,都需要程序员去重新修改代码。

    • iPhone,你可以使用USB接口连接电脑来充电,假如只有iPhone没有电脑,怎么办呢?苹果提供了iPhone电源适配器。可以使用这个电源适配器充电。这个iPhone的电源适配器就是类似我们说的适配器模式。(电源适配器就是把电源变成需要的电压,也就是适配器的作用是使得一个东西适合另外一个东西。)

    • 最典型的例子就是很多功能手机,每一种机型都自带有充电器,有一天自带充电器坏了,而且市场没有这类型充电器可买了。怎么办?万能充电器就可以解决。这个万能充电器就是适配器。

    • 当一个第三方库的API改变将会发生什么。过去你只能是咬紧牙关修改所有的客户代码,而情况往往还不那么简单。你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好。你将无法证明这些新特性的利用价值,如果这次升级意味着将要涉及到其它应用程序的客户代码。

    适配器(Adapter)模式为对象提供了一种完全不同的接口。可以运用适配器(Adapter)来实现一个不同的类的常见接口,同时避免了因升级和拆解客户代码所引起的纠纷。

    5.2 概览

    适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配或者不兼容而无法在一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装器模式(把已有的一些类包装起来,使之能有满足需要的接口)。

    5.2.1 类图

    image-20200903105806150
    • 目标角色(Target):定义Client使用的与特定领域相关的接口。用户所期待的新接口。
    • 客户角色(Client):与符合Target接口的对象协同。
    • 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。需要适配的旧代码。
    • 适配器角色(Adapter) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配。将原接口转换为目标接口。

    5.2.2 适用场景

    • 系统需要使用现有的类,而这些类的接口不符合系统的接口。
    • 想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
    • 两个类所做的事情相同或相似,但是具有不同接口的时候。
    • 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
    • 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

    5.3 实现

    该模式的核心是:Adapter适配器首先要实现目标角色的接口,并且在Adapter适配器中包含一个Adaptee被适配角色的对象,然后在目标接口角色中的旧方法中调用新接口的新方法。

    // 目标角色:用户想使用的接口
    public interface Target {
        /**
         * 这个方法将来有可能继续改进
         */
        public  void  hello();
    
        /**
         * 该方法不变
         */
        public void world();
    }
    
    // Adapter:适配器类
    public class Adapter implements Target {
        // 关联一个被适配的新接口
        private Adaptee adaptee;
        public Adapter(Adaptee adaptee) {
            this.adaptee = adaptee;
        }
    
        // 实现新老接口的适配转换
        @Override
        public void hello() {
            adaptee.greet();
        }
        @Override
        public void world() {
            adaptee.world();
        }
    }
    
    // 被适配角色:添加了新方法的角色,要将其转换为旧接口
    public class Adaptee {
        /**
         * 加入新的方法
         */
        public  void greet(){
            System.out.println("'Greet '");;
        }
        /**
         * 源类含有的方法
         */
        public void world(){
            System.out.println("'world'");;
        }
    }
    
    // 客户端
    public class Client {
        public static void main(String[] args) throws IOException {
    //        TargetInterface target = new Adapter();
    //        target.hello();
    //        target.world();
    
            Adaptee adaptee = new Adaptee();
            Adapter adapter = new Adapter(adaptee);
            adapter.hello();
            adapter.world();
        }
    }
    

    5.4 效果

    实际上,共有两种适配器模式:类适配器和对象适配器。

    类适配器和对象适配器有不同的权衡(此部分内容是《GOF设计模式》)

    • 类适配器

      • 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
      • 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
      • 仅仅引入了一个对象,并不需要额外的指针以间接得到 Adaptee。
    • 对象适配器则

      • 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)—同时工作。Adapter也可以一次给所有的Adaptee添加功能。
      • 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

    使用Adapter模式时需要考虑的其他一些因素有

    (1)Adapter的匹配程度

    对Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名 )到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度
    (2)可插入的Adapter

    当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,而这些系统对这个类的接口可能会有所不同。
    (3)使用双向适配器提供透明操作

    使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容 Adaptee的接口,因此并不是所有 Adaptee对象可以被使用的地方它都可以被使用。双向适配器提供了这样的透明性。在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。

    5.5 优点

    • 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
    • 复用了现存的类,解决了现存类和复用环境要求不一致的问题。
    • 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
    • 一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

    六、代理模式

    代理模式: 为其他真实的对象提供一种代理,以便更好地控制对这个对象的访问。它可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或添加客户需要的额外服务。

    6.1 前言

    因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗 ( if和else就是不同的两条逻辑路径) ? 你有想过限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户?以上两种需求都非常类似,并且都需要解决一个更大的问题:你如何提供一致的接口给某个对象让它可以改变其内部功能,或者是从来不存在的功能? 可以通过引入一个新的对象,来实现对真实对象的操作或者将新的对象作为真实对象的一个替身。即代理对象。

    例子1:经典例子就是网络代理,你想访问Facebook或者twitter ,如何绕过GFW,找个代理网站。

    例子2:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。

    6.2 概览

    6.2.1 类图

    image-20200905100932307

    • 抽象主题角色(Subject):

      定义真实主题角色RealSubject和抽象主题角色Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。是客户端使用的现有接口。

    • 真实对象(RealSubject):

      定义了代理角色(proxy)所代表的具体对象。是真实对象的类。

    • 代理对象(Proxy):

      代理对象通过持有真实主题RealSubject的引用,不但可以控制真实主题RealSubject的创建或删除,可以在真实主题RealSubject被调用前进行拦截,或在调用后进行某些操作。

    6.2.2 适用场景

    • 在不直接操作对象的情况下,实现对对象的访问
    • 中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
    • 增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

    6.3 实现

    6.3.1 静态代理

    /**
     * 抽象主题角色
     * 定义真实对象与代理对象共同的接口
     */
    public interface Subject {
    
        void buyHouse();
    
        void buyBed();
    
    }
    
    /**
     * 真实对象
     * 定义了真实对象的主操作
     */
    public class RealSubject implements Subject {
    
        @Override
        public void buyHouse() {
            System.out.println("买房.......");
        }
    
        @Override
        public void buyBed() {
            System.out.println("买床.......");
        }
    }
    
    /**
     * 代理对象
     * 此为静态代理模式
     * 关联了一个真实对象,在实现抽象角色中的方法时除了会调用真实对象外,还可以添加额外的操作
     */
    public class ProxySubject implements Subject {
    	// 关联一个真实对象
        private RealSubject realSubject;
        public ProxySubject(RealSubject realSubject) {
            this.realSubject = realSubject;
        }
    	// 实现Subject中的方法
        @Override
        public void buyHouse() {
            // 预处理
            preAddress("房子");
            // 调用真实对象的方法
            realSubject.buyHouse();
            // 后处理
            afterAddress("房子");
        }
        @Override
        public void buyBed() {
            // 预处理
            preAddress("床");
            // 调用真实对象的方法
            realSubject.buyBed();
            // 后处理
            afterAddress("床");
        }
    
        // 额外的方法
        //1 预处理方法
        private void preAddress(String thing) {
            System.out.println("请付"+thing+"钱.....");
        }
        //2 后处理方法
        private void afterAddress(String thing) {
            System.out.println("确认签收:"+thing+".....");
        }
    }
    
    /**
     * 客户端
     */
    public class Client {
        public static void main(String[] args) {
            RealSubject realSubject = new RealSubject();
            Subject proxySubject = new ProxySubject(realSubject);
            
            // 在不访问真实对象的前提下,通过代理对象间接地实现对其的访问
            proxySubject.buyHouse();
            proxySubject.buyBed();
        }
    }
    

    6.3.2 动态代理

    参见JAVA高级—反射与动态代理

    /**
     * 动态代理公有接口/抽象接口
     */
    public interface Human {
        String getName();
        void eat(String food);
    }
    
    /**
     * 被代理类
     */
    public class Superman implements Human {
        @Override
        public String getName() {
    
            System.out.println("超人...");
            return "超人";
        }
    
        @Override
        public void eat(String food) {
            System.out.println("超人吃:" + food);
        }
    }
    
    /**
     * 代理类:动态代理的目的是动态地创建一个代理类对象【使用java.lang.reflect.Proxy】,而不需要提前写死
     * 要想实现动态代理,需要解决:
     * 1、如何根据加载到内存中的被代理类,动态地创建一个代理类对象,通过该代理类对象实现对被代理类的额外操作
     * 2、当通过代理类的对象调用方法时,如何动态地调用被代理类中的同名方法
     */
    public class ProxyHuman {
        /**
         * 调用此方法动态地返回一个代理类的对象。解决问题1
         *
         * @param obj 被代理类对象
         * @return 代理类对象实例
         */
        public static Object getProxyInstance(Object obj) {
            // 核心接口:内部只有一个方法invoke(),调用被代理类中的同名方法,同时可添加附加操作。
            // 解决问题2
            InvocationHandler handler = new MyInvocationHandler(obj);
            // 运用反射,通过参数类的加载器、接口【被代理类所实现的接口,即公有接口】、处理器【当调用返回的代理类中的方法时,将会自动执行该处理器内的invoke()方法】,返回一个代理类对象
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
        }
    }
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    // 处理器
    public class MyInvocationHandler implements InvocationHandler {
    
        // 被代理类对象【类型不要写死】
        private Object object;
    
        public MyInvocationHandler(Object obj) {
            this.object = obj;
        }
    
        /**
         * 动态代理实现的核心
         * 当我们调用代理类对象中的方法a时,该invoke就会被自动调用
         * @param proxy     代理类对象
         * @param method    要执行的同名方法
         * @param args      方法中所需的参数
         * @return          执行方法的返回值
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            HumanUtils humanUtils = new HumanUtils();
            humanUtils.method1();
            // 通过反射【调用类中方法】,执行被代理类中的方法
            Object returnValue = method.invoke(object, args);   // 反射:将method与object被代理类对象绑定
            humanUtils.method2();
            return returnValue;
        }
    }
    
    /**
     * 客户端
     */
    public class Client {
        public static void main(String[] args) {
            System.out.println("===========动态代理===========");
            // 被代理类对象
            Superman superman = new Superman();
            // 代理类对象【动态生成】
            Human proxyInstance = (Human) ProxyHuman.getProxyInstance(superman);
            proxyInstance.getName();
    
            proxyInstance.eat("麻辣烫");
        }
    }
    

    6.4 效果

    Proxy模式可以对用户隐藏另一种称之为写时复制(copy-on-write)的优化方式,该优化与根据需要创建对象有关。拷贝一个庞大而复杂的对象是一种开销很大的操作,如果这个拷贝根本没有被修改,那么这些开销就没有必要。用代理延迟这一拷贝过程,我们可以保证只有当这个对象被修改的时候才对它进行拷贝。在实现copy-on-write时必须对实体进行引用计数。拷贝代理仅会增加引用计数。只有当用户请求一个修改该实体的操作时,代理才会真正的拷贝它。在这种情况下,代理还必须减
    少实体的引用计数。当引用的数目为零时,这个实体将被删除。copy-on-write可以大幅度的降低拷贝庞大实体时的开销。

    代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

    代理模式的缺点
    由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
    实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

    6.5 相关模式

    1)适配器模式Adapter :适配器Adapter 为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口,代理对象除了实现接口中规定的方法外,还可以定义额外的操作。如在AOP中的通知。

    1. 装饰器模式Decorator:尽管Decorator的实现部分与代理相似,但 Decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。

    七、装饰器模式

    装饰器模式:动态地给一个对象添加一些额外的职责或者行为,而不需要更改原有代码。它能够适配原始接口,并且使用组合而不是子类来扩展功能。

    7.1 前言

    • 若你从事过面向对象开发,实现给一个类或对象增加行为,可以使用继承机制,这是所有面向对象语言的一个基本特性。如果已经存在的一个类缺少某些方法,或者须要给方法添加更多的功能,你也许会仅仅继承这个类来产生一个新类—这建立在额外的代码上。通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。如果你希望改变一个已经初始化的对象的行为,你怎么办?或者,你希望继承许多类的行为,该怎么办?前一个,只能在于运行时完成,后者显然时可能的,但是可能会导致产生大量的不同的类这一可怕的事情。

    • 有时需要在现有代码中添加或删除一些功能,同时对现有的代码结构不会造成影响,并且这些删除或增加的功能有不足以做成一个子类。这种情况下可以使用装饰者模式,因为它可以在不改变现有代码的情况下满足我们的需求。装饰者模式聚合了它将要装饰的原有对象,实现了与原有对象相同的接口,代理委托原有对象的所有公共接口调用,并且在装饰者的子类中实现新增的功能。

    7.2 概览

    通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的。

    装饰器模式提供了改变子类的灵活方案。装饰器模式在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。当拥有一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复和具体子类数量的增加。

    7.2.1 类图

    image-20200906102915694

    装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为

    • **抽象组件角色(Component):**定义一个对象接口,以规范准备接受附加责任的对象,即可以给这些对象动态地添加职责
    • **具体组件角色(ConcreteComponent) :**被装饰者,定义一个将要被装饰增加功能的类。可以给这个类的对象添加一些职责
    • **抽象装饰器(Decorator):**维持一个指向构件Component对象的实例,并定义一个与抽象组件角色接口一致的接口
    • **具体装饰器角色(ConcreteDecorator):**向组件添加职责

    7.2.2 适用场景

    • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
    • 处理那些可以撤消的职责。
    • 当不能采用生成子类的方法进行扩充时。
      • 一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
      • 另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

    7.3 实现

    /**
     * 抽象组件:定义统一的基础行为
     * 也可以是一个抽象类
     */
    public interface Component {
    
        // 穿衣服
        public void wearClothes();
    
        // 去哪找衣服
        public void walkToWhere();
    
    }
    
    /**
     * 被装饰者
     * 被装饰者初始状态有些自己的装饰
     */
    public class ConcreteComponent implements Component {
    
        @Override
        public void wearClothes() {
            System.out.println("穿什么呢。。");
        }
    
        @Override
        public void walkToWhere() {
            System.out.println("去哪里呢。。");
        }
    }
    
    /**
     * 装饰者
     */
    public class Decorator implements Component {
    
    	// 聚合一个原有的“装饰对象”,这个对象只是一个与真正被装饰对象拥有相同方法的接口
        private Component component;
    
        public Decorator(Component component) {
            this.component = component;
        }
    
    	// 实现原对象的方法
        @Override
        public void wearClothes() {
            component.wearClothes();
        }
    
        @Override
        public void walkToWhere() {
            component.walkToWhere();
        }
    }
    
    /**
     * 具体装饰类
     * 即抽象装饰类的子类:可以定义多种不同的功能
     */
    public class ConcreteDecorator01 extends Decorator{
    
        public ConcreteDecorator01(Component component) {
            super(component);
        }
    
        // 额外的方法
        public void goHome() {
            System.out.println("进01。。");
        }
        public void findMap() {
            System.out.println("01找找Map。。");
        }
    
        // 重写,将额外的方法添加至方法中
        @Override
        public void wearClothes() {
            super.wearClothes();
            goHome();
        }
    
        @Override
        public void walkToWhere() {
            super.walkToWhere();
            findMap();
        }
    }
    
    public class ConcreteDecorator02 extends Decorator {
    
        public ConcreteDecorator02(Component component) {
            super(component);
        }
    
        public void goClothesPress() {
            System.out.println("去02找找看。。");
        }
    
        public void findPlaceOnMap() {
            System.out.println("在02上找找。。");
        }
    
        @Override
        public void wearClothes() {
            super.wearClothes();
            goClothesPress();
        }
    
        @Override
        public void walkToWhere() {
            super.walkToWhere();
            findPlaceOnMap();
        }
    
    
    }
    
    public class ConcreteDecorator03 extends Decorator{
    
        public ConcreteDecorator03(Component component) {
            super(component);
        }
    
        public void findClothes() {
            System.out.println("找到一件03。。");
        }
    
        public void findTheTarget() {
            System.out.println("在03上找到神秘花园和城堡。。");
        }
    
        @Override
        public void wearClothes() {
            super.wearClothes();
            findClothes();
        }
    
        @Override
        public void walkToWhere() {
            super.walkToWhere();
            findTheTarget();
        }
    
    }
    
    /**
    * 客户端
    */
    public class Client {
    
        public static void main(String[] args) {
    
            Component concreteComponent = new ConcreteComponent();
    
    //        Decorator concreteDecorator03 = new ConcreteDecorator03(concreteComponent);
    //        concreteDecorator03.wearClothes();
    //        concreteDecorator03.walkToWhere();
    
            // 装饰者模式可以按照链式方式进行执行
            // 初始化时:01->02->03
            Decorator decorator = new ConcreteDecorator03(
                                                        new ConcreteDecorator02(
                                                            new ConcreteDecorator01(
                                                                concreteComponent
                                                                                    )
                                                                                )
                                                        );
    
            // 运行时:03->
            //         	03的super:即02->
            //         	02的super:即01->
            //         	01的super:即component->
            //         	执行被装饰者的基本方法
            // 然后按照01->02->03的顺序调用额外新增的装饰方法
            decorator.wearClothes();
            decorator.walkToWhere();
        }
    }
    

    7.3.1 关键点

    • Decorator抽象类中,持有Component接口,方法全部委托给该接口调用,目的是交给该接口的实现类即子类进行调用。
    • Decorator抽象类的子类(具体装饰者),里面都有一个构造方法调用super(human),这一句就体现了抽象类依赖于子类实现即抽象依赖于实现的原则。因为构造里面参数都是Component接口,只要是该Component的实现类都可以传递进去,即表现出Decorator decorator = new ConcreteDecorator03(new ConcreteDecorator02(new ConcreteDecorator01(concreteComponent)));这种结构的样子。所以当调用decorator. wearClothes();decorator. walkToWhere()的时候,又因为每个具体装饰者类中,都先调用super.wearClothes和super.walkToWhere()方法,而该super已经由构造传递并指向了具体的某一个装饰者类(这个可以根据需要调换顺序),那么调用的即为装饰类的方法,然后才调用自身的装饰方法,即表现出一种装饰、链式的类似于过滤的行为。
    • 具体被装饰者类,可以定义初始的状态或者初始的自己的装饰,后面的装饰行为都在此基础上一步一步进行点缀、装饰。
    • 装饰者模式的设计原则为:对扩展开放、对修改关闭,这句话体现在我如果想扩展被装饰者类的行为,无须修改装饰者抽象类,只需继承装饰者抽象类,实现额外的一些装饰或者叫行为即可对被装饰者进行包装。所以:扩展体现在继承、修改体现在子类中,而不是具体的抽象类,这充分体现了依赖倒置原则,这是自己理解的装饰者模式。

    7.4 效果

    7.4.1 装饰模式的特点

    • 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互

    • 装饰对象包含一个真实对象的索引(reference)

    • 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象

    • 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展

    7.4.2 优缺点

    • 比静态继承更灵活: 与对象的静态继承(多重继承)相比, Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的 Decorator类,这就使得你可以对一些职责进行混合和匹配。使用Decorator模式可以很容易地重复添加一个特性。
    • 避免在层次结构高层的类有太多的特征 Decorator模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用 Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时更易于不依赖于 Decorator所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的 Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。
    • Decorator与它的Component不一样 Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰不应该依赖对象标识。
    • 有许多小对象 采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。

    7.5 相关模式

    7.5.1 装饰模式和适配器模式

    这是两个没有什么关联的模式,放到一起来说,是因为它们有一个共同的别名:Wrapper。 这两个模式功能上是不一样的,适配器模式是用来改变接口的,而装饰模式是用来改变对象功能的。

    7.5.2 装饰模式和策略模式

    这两个模式可以组合使用。 策略模式也可以实现动态的改变对象的功能,但是策略模式只是一层选择,也就是根据策略选择一下具体的实现类而已。而装饰模式不是一层,而是递归调用,无数层都可以,只要组合好装饰器的对象组合,那就可以依次调用下去,所以装饰模式会更灵活。 而且策略模式改变的是原始对象的功能,不像装饰模式,后面一个装饰器,改变的是经过前一个装饰器装饰过后的对象,也就是策略模式改变的是对象的内核,而装饰模式改变的是对象的外壳。 这两个模式可以组合使用,可以在一个具体的装饰器里面使用策略模式,来选择更具体的实现方式。

    7.5.3 装饰模式和组合模式

    这两个模式有相似之处,都涉及到对象的递归调用,从某个角度来说,可以把装饰看成是只有一个组件的组合。 但是它们的目的完全不一样,装饰模式是要动态的给对象增加功能;而组合模式是想要管理组合对象和叶子对象,为它们提供一个一致的操作接口给客户端,方便客户端的使用。

    7.6 总结

    • 使用装饰器设计模式设计类的目标是: 不必重写任何已有的功能性代码,而是对某个基于对象应用增量变化。

    • 装饰器设计模式采用这样的构建方式: 在主代码流中应该能够直接插入一个或多个更改或“装饰”目标对象的装饰器,同时不影响其他代码流。

    • Decorator模式采用对象组合而非继承的手法,实现了在运行时动态的扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了单独使用继承带来的“灵活性差”和“多子类衍生问题”。同时它很好地符合面向对象设计原则中“优先使用对象组合而非继承”和“开放-封闭”原则。

    也许装饰器模式最重要的一个方面是它的超过继承的能力。“问题”部分展现了一个使用继承的子类爆炸。

    基于装饰器模式的解决方案,UML类图展现了这个简洁灵活的解决方案。

    八、组合模式

    组合模式(Composite Pattern)又叫做部分-整体模式,模糊了简单元素和复杂元素的概念 ,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。组合模式让你可以优化处理递归或分级数据结构。

    8.1 前言

    在数据结构里面,树结构是很重要,我们可以把树的结构应用到设计模式里面。

    • 多级树形菜单

    • 文件和文件夹目录。

      image-20200907143204452

      我们对于这个图片肯定会非常熟悉,这两幅图片我们都可以看做是一个文件结构,对于这样的结构我们称之为树形结构。在数据结构中我们了解到可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。我们可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是**由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。**这就是组合模式的设计动机:组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。

    • 剪发卡:首先,一张卡可以在总部,分店,加盟店使用,那么总部可以刷卡,分店也可以刷卡,加盟店也可以刷卡,这个属性结构的店面层级关系就明确啦。 那么,总店刷卡消费与分店刷卡消费是一样的道理,那么总店与分店对会员卡的使用也具有一致性。

    我们可以使用简单的对象组合成复杂的对象,而这个复杂对象又可以组合成更大的对象。我们可以把简单这些对象定义成类,然后定义一些容器类来存储这些简单对象。客户端代码必须区别对象简单对象和容器对象,而实际上大多数情况下用户认为它们是一样的。对这些类区别使用,就使得程序更加复杂。递归使用的时候更麻烦,而我们如何使用递归组合,使得用户不必对这些类进行区别呢?

    8.2 概览

    组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

    有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

    上面的图展示了计算机的文件系统,文件系统由文件和目录组成,目录下面也可以包含文件或者目录,计算机的文件系统是用递归结构来进行组织的,对于这样的数据结构是非常适用使用组合模式的。

    在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。

    8.2.1 类图

    image-20200907100731129

    • **抽象构件角色(Component):**是组合中的对象声明接口,在适当的情况下,声明所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。这个接口可以用来管理所有的子对象。在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
    • **树叶构件角色(Leaf):**在组合树中表示叶节点对象,叶节点没有子节点。并在组合中定义图元对象的行为。
    • **树枝构件角色(Composite):**定义有子部件的那些部件的行为以及存储子部件。在Component接口中实现与子部件有关的操作。
    • **客户角色(Client):**通过Component接口操纵组合部件的对象。

    从模式结构中我们看出了叶子节点和容器对象都实现Component接口,这也是能够将叶子对象和容器对象一致对待的关键所在。

    8.2.2 分类

    • 将管理子元素的方法定义在Composite类中
    • 将管理子元素的方法定义在Component接口中,此时这样Leaf类就需要对这些方法空实现。

    8.2.3 适用场景

    • 当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。

    8.3 实现

    在文件系统中,可能存在很多种格式的文件,如果图片,文本文件、视频文件等等,这些不同的格式文件的浏览方式都不同,同时对文件夹的浏览就是对文件夹中文件的浏览,但是对于客户而言都是浏览文件,两者之间不存在什么差别,现在只用组合模式来模拟浏览文件。

    image-20200907143523221

    // 抽象角色:文件类,统一的接口,其实现有文件夹类与文件
    public abstract class File {
        // 文件名称
        String name;
        
        public File(String name){
            this.name = name;
        }
        
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public abstract void display();
    }
    
    // 组合类:文件夹类,内部含有文件类对象(文件夹和文件)
    public class Folder extends File{
        // 文件夹中的内容列表(文件夹或文件)
        // 该部分在调用了Folder的构造方法后执行,创建一个新的子文件列表
        private List<File> files = new ArrayList<File>();
        
        public Folder(String name){
            super(name);
        }
        
        /**
         * 浏览文件夹中的文件
         */
        public void display() {
            for(File file : files){
                file.display();
            }
        }
        
        /**
         * 向文件夹中添加文件
         */
        public void add(File file){
            files.add(file);
        }
        
        /**
         * 从文件夹中删除文件
         */
        public void remove(File file){
            files.remove(file);
        }
    }
    
    // 叶子节点:具体的文件类型
    public class TextFile extends File{
    
        public TextFile(String name) {
            super(name);
        }
    
        public void display() {
            System.out.println("这是文本文件,文件名:" + super.getName());
        }
        
    }
    
    // 叶子节点:具体的文件类型
    public class ImagerFile extends File{
    
        public ImagerFile(String name) {
            super(name);
        }
    
        public void display() {
            System.out.println("这是图像文件,文件名:" + super.getName());
        }
    
    }
    
    // 叶子节点:具体的文件类型
    public class VideoFile extends File{
    
        public VideoFile(String name) {
            super(name);
        }
    
        public void display() {
            System.out.println("这是影像文件,文件名:" + super.getName());
        }
    
    }
    
    // 客户端
    public class Client {
        public static void main(String[] args) {
            /**
             * 我们先建立一个这样的文件系统
             *                  总文件
             *                  
             *   a.txt    b.jpg                   c文件夹              
             *                      c_1.text  c_1.rmvb    c_1.jpg   
             *                                                       
             */ 
            // 总文件夹
            Folder zwjj = new Folder("总文件夹");
            // 向总文件夹中放入三个文件:1.txt、2.jpg、1文件夹
            TextFile aText= new TextFile("a.txt");
            ImagerFile bImager = new ImagerFile("b.jpg");
            Folder cFolder = new Folder("C文件夹");
            
            // 向总文件夹中添加文件
            zwjj.add(aText);
            zwjj.add(bImager);
            zwjj.add(cFolder);
            
            // 向C文件夹中添加文件:c_1.txt、c_1.rmvb、c_1.jpg 
            TextFile cText = new TextFile("c_1.txt");
            ImagerFile cImage = new ImagerFile("c_1.jpg");
            VideoFile cVideo = new VideoFile("c_1.rmvb");
            
            cFolder.add(cText);
            cFolder.add(cImage);
            cFolder.add(cVideo);
             
            // 遍历C文件夹
            cFolder.display();
            // 将c_1.txt删除
            cFolder.remove(cText);
            System.out.println("-----------------------");
            cFolder.display();
        }
    }
    

    8.4 效果

    8.4.1 特点

    • 定义了包含基本对象和组合对象的类层次结构

      基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。

    • 简化客户代码

      客户可以一致地使用组合结构和单个对象。通常用户不知道 (也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码 , 因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。

    • 使得更容易增加新类型的组件

      新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。

    • 使你的设计变得更加一般化

      容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。

    8.4.2 优缺点

    (1)优点
    • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
    • 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
    • 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
    • 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
    (2)缺点

    使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联

    8.5 相关模式

    • 装饰模式Decorator经常与Composite模式一起使用。当装饰和组合一起使用时,它们通常有一个公共的父类。因此装饰必须支持具有 Add、Remove和GetChild 操作的Component接口。

    • 享元模式Flyweight让你共享组件,但不再能引用他们的父部件。

    • 迭代器模式Iterator可用来遍历Composite。

    • 观察者模式Visitor将本来应该分布在Composite和L e a f类中的操作和行为局部化。

    8.6 总结

    1. 组合模式用于将多个对象组合成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。

    2. 组合对象的关键在于它定义了一个抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建进行编程,无须知道他是叶子对象还是容器对象,都是一致对待。

    3. 组合模式虽然能够非常好地处理层次结构,也使得客户端程序变得简单,但是它也使得设计变得更加抽象,而且也很难对容器中的构件类型进行限制,这会导致在增加新的构件时会产生一些问题。

    九、桥接模式

    桥接模式即将抽象部分与它的实现部分分离开来,使他们都可以独立变化。

    9.1 前言

    在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?

    设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:

    • 第一种设计方案是为每一种形状都提供一套各种颜色的版本。

      image-20200908154014069
    • 第二种设计方案是根据实际需要对形状和颜色进行组合。

      image-20200908154031191

    对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。

    如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?

    9.2 概览

    桥接模式即将抽象部分与它的实现部分分离开来,使他们都可以独立变化。桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。

    将抽象部分与他的实现部分分离并不是将抽象类与他的派生类分离,而是抽象类和它的派生类用来实现自己的对象。

    • 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。

    • 实现化:针对抽象化给出的具体实现。它和抽象化是一个互逆的过程,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。

    • 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。

    将抽象部分与他的实现部分分离就是实现系统可能有多个角度分类,每一种角度都可能变化,那么把这种多角度分类给分离出来让他们独立变化,减少他们之间耦合。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

    9.2.1 类图

    image-20200908154923799
    • **抽象类(Abstraction):**定义抽象类的接口,维护一个指向Implementor类型对象的指针
    • **扩充抽象类(RefinedAbstraction):**扩充由Abstraction定义的接口
    • **实现类接口(Implementor):**定义实现类的接口,该接口不一定要与 Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲, Implementor接口仅提供基本操作,而 Abstraction则定义了基于这些基本操作的较高层次的操作,如形状与上色(形状就为高级操作)
    • **具体实现类(ConcreteImplementor):**实现Implementor接口并定义它的具体实现

    9.2.2 适用场景

    • 你不希望在抽象和他的实现部分之间有一个固定的绑定关系,如在程序的运行时刻实现部分应该可以被选择或者切换。

    • 类的抽象以及他的实现都可以通过生成子类的方法加以扩充。这时bridge模式使你可以对不同的抽象接口和实现部分进行组合,并对他们进行扩充。

    • 对一个抽象的实现部分的修改应该对客户不产生影响,即客户的代码不需要重新编译。

    • 你想对客户完全隐藏抽象的实现部分。

    • 你想在多个实现间共享实现,但同时要求客户并不知道这一点。

    9.3 实现

    /**
     * 抽象类
     * 形状类,内聚合有一个实现类接口对象Color
     * 此为将抽象部分与他的实现部分分离,以实现更多层次的组合
     */
    public abstract class Shape {
    
        // 采用聚合的方式,而不是让实现类去继承本抽象类
        // 聚合一个实现类接口对象,以实现上色的高层次操作
        private Color color;
    
        public Color getColor() {
            return color;
        }
    
        public void setColor(Color color) {
            this.color = color;
        }
    
        public abstract void draw();
    }
    
    /**
     * 扩充抽象类
     * 具体的图形类
     * 抽象角色的扩充
     */
    public class Circle extends Shape {
    
        @Override
        public void draw() {
            super.getColor().paint("这是一个圆....");
        }
    }
    
    public class Rectangle extends Shape {
    
        @Override
        public void draw() {
            super.getColor().paint("这是一个长方形....");
        }
    }
    
    public class Square extends Shape {
        @Override
        public void draw() {
            super.getColor().paint("这是一个正方形....");
        }
    }
    
    /**
     * 实现类接口,该接口并不继承于抽象类,而是将该实现类接口聚合至抽象类中
     * 该接口与抽象类的方法不一样
     * 是一种对于抽象类的基本操作,抽象类则含有该接口对象,以实现更高层次的操作
     */
    public interface Color {
        public void paint(String shape);
    }
    
    /**
     * 具体实现类
     * 实现接口中的方法
     */
    public class Black implements Color {
        @Override
        public void paint(String shape) {
            System.out.println(shape+"黑色的");
        }
    }
    
    public class Gray implements Color {
        @Override
        public void paint(String shape) {
            System.out.println(shape+"灰色的");
        }
    }
    
    public class Red implements Color {
        @Override
        public void paint(String shape) {
            System.out.println(shape+"红色的");
        }
    }
    
    public class Client {
    
        public static void main(String[] args) {
            // 从两个角度定义
            //1 上色
            Color black = new Black();
    
            //2 图形选择
            Shape circle = new Circle();
    
            circle.setColor(black);
            circle.draw();
        }
    }
    

    9.4 特点

    9.4.1 优点

    Bridge模式有以下一些优点:

    • 分离接口及其实现部分

      一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译 Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可

    • 提高可扩充性

      由于模式中的抽象类与实现类接口均可被扩展,所以要添加或删除功能将变得更方便。你可以独立地对Abstraction和Implementor层次结构进行扩充,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。同时,方便两部分的任意组合,以实现不同的效果。

    • 实现细节对客户透明

      你可以对客户隐藏实现细节,例如共享 Implementor对象以及相应的引用计数机制(如果有的话)

    9.4.2 缺点

    桥接模式的缺点

    • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程

    • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性

    9.5 相关模式

    • 抽象工厂Abstract Factory 模式可以用来创建和配置一个特定的Bridge模式。

    • 适配器模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然而,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

    • 桥接模式与装饰的区别:

      • 装饰模式:

        这两个模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。但是它们解决的方法却各有不同,装饰模式把子类中比基类中多出来的部分放到单独的类里面,以适应新功能增加的需要,当我们把描述新功能的类封装到基类的对象里面时,就得到了所需要的子类对象,这些描述新功能的类通过组合可以实现很多的功能组合

      • 桥接模式:

        桥接模式则把原来的基类的实现化细节抽象出来,在构造到一个实现化的结构中,然后再把原来的基类改造成一个抽象化的等级结构,这样就可以实现系统在多个维度上的独立变化 。

    9.6 总结

    1. 桥接模式实现了抽象化与实现化的脱耦。他们两个互相独立,不会影响到对方
    2. 对于两个独立变化的维度,使用桥接模式再适合不过了
    3. 对于“具体的抽象类”所做的改变,是不会影响到客户

    十、外观模式

    为复杂的子系统提供单一的统一接口。通过为最重要的用力提供接口,能够简化大型和复杂系统的使用。

    10.1 前言

    现代的软件系统都是比较复杂的,设计师处理复杂系统的一个常见方法便是将其“分而治之”,把一个系统划分为几个较小的子系统。如果把医院作为一个子系统,按照部门职能,这个系统可以划分为挂号、门诊、划价、化验、收费、取药等。看病的病人要与这些部门打交道,就如同一个子系统的客户端与一个子系统的各个类打交道一样,不是一件容易的事情。

    首先病人必须先挂号,然后门诊。如果医生要求化验,病人必须首先划价,然后缴费,才可以到化验部门做化验。化验后再回到门诊室。

    image-20200910091824364

    上图描述的是病人在医院里的体验,图中的方框代表医院。

    解决这种不便的方法便是引进门面模式,医院可以设置一个接待员的位置,由接待员负责代为挂号、划价、缴费、取药等。这个接待员就是门面模式的体现,病人只接触接待员,由接待员与各个部门打交道。

    image-20200910091850668

    10.2 概述

    许多复杂的系统可以简化为几个子系统暴露的用例接口,这样可以让客户端代码不需要知道子系统的内部结构与联系。即客户端代码和复杂的子系统解耦,并且能让开发人员更简单地使用子系统,这被称为外观模式,其中外观对象负责暴露所有子系统的功能。与隐藏了对象的内部结构和逻辑的封装类似,外观模式隐藏了子系统的复杂内部结构,只向外提供可访问的通用接口,这样做的结果是用户只能访问由外观模式向外提供的功能,无法随意使用或者重用子系统内部的某些具体功能函数。

    外观模式需要适配多个内部子系统接口到一个客户端代码接口上,它通过创建一个新的接口来实现这一点,该接口由适配器模式来适配现有的接口(有时需要多个旧类来为新代码提供所需功能)。

    10.2.1 类图

    **外观模式:**为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。

    门面模式没有一个一般化的类图描述,最好的描述方法实际上就是以一个例子说明。

    image-20200910092749645

    由于门面模式的结构图过于抽象,因此把它稍稍具体点。假设子系统内有三个模块,分别是ModuleA、ModuleB和ModuleC,它们分别有一个示例方法,那么此时示例的整体结构图如下:

    image-20200910092824021

    • **外观角色(Facade):**是模式的核心,他被客户client角色调用,知道各个子系统的功能。同时根据客户角色已有的需求预订了几种功能组合。

    • **子系统角色(Subsystem classes):**实现子系统的功能,并处理由Facade对象指派的任务。对子系统而言,Facade和Client角色是未知的,没有Facade的任何相关信息;即没有指向Facade的实例。

    • **客户角色(Client):**调用Facade角色获得完成相应的功能。

    10.2.2 适用场景

    • 如果你希望为一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式,使用外观对象来实现大部分客户需要的功能,从而简化客户的使用
    • 如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性
    • 如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层间调用,也可以松散层次之间的依赖关系

    10.3 实现

    /**
     * 复杂的子系统类,这些子系统类是独立的,其内部的操作是很复杂的
     * 电视类
     */
    public class Television {
    
        public void on(){
            System.out.println("打开了电视....");
        }
    
        public void off(){
            System.out.println("关闭了电视....");
        }
    
    }
    
    /**
     * 电灯类
     */
    public class Light {
    
        public void on(){
            System.out.println("打开了电灯....");
        }
    
        public void off(){
            System.out.println("关闭了电灯....");
        }
    
    }
    
    /**
     * 空调类
     */
    public class AirCondition {
    
        public void on(){
            System.out.println("打开了空调....");
        }
    
        public void off(){
            System.out.println("关闭了空调....");
        }
    
    }
    
    /**
     * 屏幕类
     */
    public class Screen {
    
        public void up(){
            System.out.println("升起银幕....");
        }
    
        public void down(){
            System.out.println("下降银幕....");
        }
    
    }
    
    /**
     * 外观类
     * 关联所有的子系统类对象
     */
    public class Facade {
    
        // 关联子系统类对象
        Television television;
        Light light;
        AirCondition airCondition;
        Screen screen;
    
        public Facade(Television television, 
                      Light light, 
                      AirCondition airCondition, 
                      Screen screen) 
        {
            this.television = television;
            this.light = light;
            this.airCondition = airCondition;
            this.screen = screen;
        }
    
        // 定义任意的方法可以任意组合任意的操作
        public void on(){
            //1 打开全部的设备
            television.on();
            light.on();
            airCondition.on();
            screen.up();
        }
    
        public void on01(){
            //2 打开部分设备
            television.on();
            light.on();
        }
    
        public void off(){
            television.off();
            light.off();
            airCondition.off();
            screen.down();
        }
    
    }
    
    /**
     * 客户端类
     */
    public class Client {
    
        public static void main(String[] args) {
    
            //实例化组件
            Light light = new Light();
            Television tv = new Television();
            AirCondition ac = new AirCondition();
            Screen screen = new Screen();
    
            // 创建外观类
            Facade facade = new Facade(tv,light,ac,screen);
    
            facade.on();
            facade.off();
    
        }
    
    }
    

    在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只有一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每一个子系统都有一个门面类,整个系统可以有数个门面类。

    10.4 特点

    10.4.1 优点

    • 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。

    • 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。

    • 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

    • 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。

    10.4.2 缺点

    • 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。

    • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

    10.5 相关模式

    • **抽象工厂模式:**Abstract Factory式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。 Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。

    • **中介模式:**Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道Facade的存在。通常来讲,仅需要一个Facade对象,因此Facade对象通常属于Singleton模式。

    • Adapter模式:

      适配器模式是将一个接口通过适配来间接转换为另一个接口。外观模式的话,其主要是提供一个整洁的一致的接口给客户端。

    10.6 总结

    • 根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。

    • 外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,外观类充当了客户类与子系统类之间的“第三者”,同时降低客户类与子系统类的耦合度。外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器。

    • 外观模式最大的缺点在于违背了“开闭原则”。当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。

    • 外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。

    • 外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。

    • 不要试图通过外观类为子系统增加新行为 ,不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。

    十一、享元模式

    运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。

    11.1 前言

    在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象。

    • 一个文本系统,每个字母定一个对象,那么大小写字母一共就是52个,那么就要定义52个对象。如果有一个1M的文本,那么字母是何其的多,如果每个字母都定义一个对象那么内存早就爆了。那么如果要是每个字母都共享一个对象,那么就大大节约了资源。
    • 在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String a=“abc”,其中"abc"就是一个字符串常量。
    • 五子棋游戏,五子棋同围棋一样,包含多个“黑”或“白”颜色的棋子,所以用享元模式比较好。

    11.2 概述

    面向对象可以非常方便的解决一些扩展性的问题,但是在这个过程中系统务必会产生一些类或者对象,如果系统中存在对象的个数过多时,将会导致系统的性能下降。对于这样的问题解决最简单直接的办法就是减少系统中对象的个数。

    享元模式提供了一种解决方案,使用共享技术实现相同或者相似对象的重用。也就是说实现相同或者相似对象的代码共享。共享模式是支持大量细粒度对象的复用,所以享元模式要求能够共享的对象必须是细粒度对象。

    • 内部状态:在享元对象内部不随外界环境改变而改变的共享部分
    • 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态

    在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也成为细粒度对象。

    由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。

    如何利用享元模式呢?这里我们只需要将他们少部分的不同的部分当做参数移动到类实例的外部去,然后再方法调用的时候将他们传递过来就可以了。这里也就说明了一点:内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑,当调用Flyweight对象的操作时,将该状态传递给它。

    11.2.1 类图

    image-20200912092226017

    • **Flyweight:**抽象享元类。所有具体享元类的超类或者接口,通过这个接口,Flyweight可以接受并作用于外部状态
    • **ConcreteFlyweight:**具体享元类。指定内部状态,为内部状态增加存储空间
    • **UnsharedConcreteFlyweight:**非共享具体享元类。指出那些不需要共享的Flyweight子类
    • FlyweightFactory: 享元工厂类。用来创建并管理Flyweight对象,它主要用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory就会提供一个已经创建的Flyweight对象或者新建一个(如果不存在)

    享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用唯一标识码(key)判断。用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度。

    11.2.2 适用场景

    • 如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。

    • 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。

    • 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

    11.3 实现

    假如我们有一个绘图的应用程序,通过它我们可以绘制出各种各样的形状、颜色的图形,那么这里形状和颜色就是内部状态了,通过享元模式我们就可以实现该属性的共享了。

    /**
     * 抽象的享元类
     */
    public abstract class Shape {
    
        public abstract void draw(User user);
    
    }
    
    /**
     * 具体享元类
     * 内部对象包含有内部状态信息:这些信息都是细粒度对象信息,如形状与颜色
     * 内部对象>=内部状态
     */
    public class Circle extends Shape {
    
        private String color;
        public Circle(String color){
            this.color = color;
        }
    
        public void draw(User user) {
            System.out.println(user.getName()+"画了一个" + color +"的圆形");
        }
    }
    
    /**
     * 非共享具体享元类
     * 外部对象:包含有外部状态的信息
     */
    public class User {
    
        private String name;
        //创建User构造函数,获取用户名
        public User (String name)
        {
            this.name = name;
        }
        public String getName()
        {
            return name;
        }
    
    }
    
    /**
     * 享元工厂类
     * 使用Map结构来提供一个用于存储享元对象的享元池
     */
    public class FlyweightFactory {
    
        static Map<String, Shape> shapes = new HashMap<String, Shape>();
    
        public static Shape getShape(String key){
            Shape shape = shapes.get(key);
            //如果shape==null,表示不存在,则新建,并且保持到共享池中
            if(shape == null){
                shape = new Circle(key);
                shapes.put(key, shape);
            }
            return shape;
        }
    
        public static int getSum(){
            return shapes.size();
        }
    
    }
    
    /**
     * 客户端
     */
    public class Client {
    
        public static void main(String[] args) {
    
            /**
             * 客户端定义外部状态类
             * 根据需要由客户端进行配置
             */
            User zdp = new User("zdp");
            User zjx = new User("zjx");
    
            // 由于享元工厂中定义的是静态方法,所以可以使用类名+方法名的方式进行调用
            Shape shape1 = FlyweightFactory.getShape("红色");
            shape1.draw(zdp);
    
            Shape shape2 = FlyweightFactory.getShape("灰色");
            shape2.draw(zdp);
    
            Shape shape3 = FlyweightFactory.getShape("绿色");
            shape3.draw(zdp);
    
            Shape shape4 = FlyweightFactory.getShape("红色");
            shape4.draw(zdp);
    
            Shape shape5 = FlyweightFactory.getShape("灰色");
            shape5.draw(zjx);
    
            Shape shape6 = FlyweightFactory.getShape("灰色");
            shape6.draw(zjx);
    
            System.out.println("一共绘制了"+FlyweightFactory.getSum()+"种颜色的圆形");
        }
    
    }
    

    image-20200912103627092

    11.4 特点

    11.4.1 优点

    • 享元模式的优点在于它能够极大的减少系统中对象的个数。

    • 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。

    11.4.2 缺点

    • 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。

    • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

    11.5 相关模式

    • 创建型模式:

      创建对象都需要花费时间和资源。最好的例子就是创建Java常量字符串,因为从不创建实例,他们返回的是不可变的缓存实例。应用程序使用对象池来达到加速创建的目的,并维持一个一个较低的内存占有率。建造者模式与享元模式的区别是,创建型模式是保存可变域对象的容器,而享元模式是保存不可变域对象。由于是不可变的,因此他们的内部状态是在创建时设置的,并且在每个方法调用时从外部给出外部状态。

    • 外观模式:

      享元模式知道如何制作很多的小对象,而外观模式时单个对象简化并隐藏由许多对象组成的子系统的复杂性。

    11.6 总结

    • 享元模式可以极大地减少系统中对象的数量。但是它可能会引起系统的逻辑更加复杂化。

    • 享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。

    • 内部状态为不变共享部分,存储于享元享元对象内部,而外部状态是可变部分,它应当油客户端来负责。

    展开全文
    zjxBayern 2021-07-09 22:00:43
  • OnafioO 2017-01-11 16:50:05
  • weixin_44050144 2021-02-28 16:15:53
  • qqxx6661 2018-11-17 22:54:34
  • attack_breast 2021-01-12 10:13:48
  • xufangfanglemon 2011-04-18 16:31:00
  • Aatroy 2020-11-27 21:26:47
  • zyq522376829 2017-12-13 14:39:53
  • m0_37741420 2020-06-06 11:20:24
  • csdn565973850 2021-09-24 14:00:55
  • u013362192 2021-03-24 22:51:38
  • chengbaobao520 2021-10-04 21:02:40
  • ym1080 2019-12-10 14:48:29
  • MQiangLu 2021-04-22 22:49:00
  • qq_36553031 2018-10-11 20:05:10
  • zhaohongfei_358 2021-03-22 16:57:23
  • wts563540 2021-02-02 16:50:25
  • chenjian723122704 2021-10-13 11:39:19
  • Nimrod__ 2021-01-14 16:55:46
  • weixin_44577346 2021-06-06 09:09:38
  • qq_43454016 2021-03-25 11:18:23
  • m0_46741750 2021-04-18 23:38:58
  • aaa_bbb_ccc_123_456 2020-04-07 21:37:27
  • zzqtty 2021-03-12 14:12:51
  • will130 2016-05-29 23:31:17
  • weixin_42524530 2021-06-11 13:31:08

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,887
精华内容 6,354
热门标签
关键字:

兼容性简化的颜色模式

友情链接: zoomscale.zip