Java 设计模式
2021-09-29 17:57:07 0 举报
AI智能生成
Java 设计模式
作者其他创作
大纲/内容
设计模式内容--经典面试题
1), 使用UML类图画出原型模式核心角色
2), 原型模型的深拷贝和浅拷贝是什么?,并写出深拷贝的两种方式的源码(重写clone 方法实现深拷贝, 使用序列化来实现深拷贝)
3), 在spring 框架中,哪里使用原型模式,并对源码进行分析
4),设计模式的七大原则 要求,1,七大原则的核心思想 2,能够以类图的说明设计原则
1),单一职责
2),接口隔离
3),依赖倒转
4),里氏替换
5),开闭原则
6),迪米特法则
7),合成复用原则
5),在项目开发过程中, 你在哪里使用了 ocp原则
6),在Spring 中框架中,哪里使用了 解释器设计模式,并做源码级别的分析
7),单例设计模式一共有8种写法
1), 饿汉式 2种
2), 懒汉式 3种
3), 双重检查
4),静态内部类
5),枚举
8),设计模式在软件中哪里?
面向对象(oo) =>功能模块编写[设计模式+算法+数据结构] => 框架[使用多种设计模式] => 架构[服务器集群技术]
用过什么设计模式, 怎么使用设计模式 ,设计模式了解决了什么问题
设计模式概述
软件使用设计模式
1), 代码重用性
2), 可读性
3), 可扩展
4),可靠性
5),使程序高内聚低耦合的特性
代码使用设计模式
1),单一职责
2),接口隔离
3),依赖倒转
4),里氏替换
5),开闭原则
6),迪米特法则
7),合成复用职责
七大设计原则
单一职责原则
对类来说,即一个类只负责一项职责,如果类A负责两个不同职责,职责1和职责2,当职责1因需求而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分为 A1(类/方法) 和 A2(类/方法)
注意事项和细节
1),降低类的复杂度,一个类只负责一项职责
2), 提高类的可读性,可维护性
3), 降低变更引起的风险
4),通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则: 只有类中方法数量足够少,可以在方法级别保持单一职责原则
问题代码
优化后的方式2
最终方式3
接口隔离原则
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该是建立在最小的接口上
PlantUML 代码为
PlantUML 优化代码为
依赖倒转(倒置)原则
概念
高层模块不应该依赖底层模块,二者都应该依赖其抽象
抽象不应该依赖细节,细节应该依赖抽象
依赖倒转的中心思想是面向接口编程
依赖倒转原则是基于这样的设计理念,相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构比细节为基础的架构要稳定的多,在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
使用接口或者抽象类的目的是指定好规范, 而不涉及任何具体的操作, 把展示细节的任务交给他们的实现类去实现
问题1
优化后代码1
注意事项和细节
1),底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
2),变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
3),继承时遵循里氏替换原则
里氏替换原则
OOP中的继承性的思考和说明
1), 继承包含这样一层意思, 父类中已经实现好的方法, 实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这样些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
2),继承在给程序带来便利的同时,也带来了弊端,比如使用继承会给程序带来侵入性,程序的可移植性降低,耦合性增加,如果一个类被其它类所继承,,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能那都有可能产生故障
在编程中如何正确的使用继承? =>里氏替换原则
概念
如果对每个类型为T1的对象O1 , 都有类型为T2的对象O2 , 使得T1 定义的所有程序 P 在所有的对象 O1都代换成 O2 , 程序P的行为没有发生变化, 那么类型T2 是类型 T1 的子类型, 换句话说 , 所有引用基类的地方必须能透明地使用其子类的对象
在使用继承时,遵循里氏替换原则, 在子类中尽量不要重写父类的方法
里氏替换原则告诉我们, 继承实际上让两个类耦合性增加了, 在适当的情况下,可以通过聚合,组合,依赖来解决问题
开闭原则
概念
1), 一个软件实体如类,模块和函数对扩展(对提供方)开放,对修改(使用方)关闭,用抽象构建框架,用实现扩展细节
2),当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
3),编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
迪米特法则
概述
1),一个对象应该对其它对象保持最少的了解
2),类与类之间关系密切,耦合度越大
3),迪米特法则又叫 最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说, 对于被依赖的类不管多复杂,都尽量将逻辑封装在类内部, 对外除了提供 public 方法外, 不对外泄漏任何信息
4), 迪米特法则还有更简单的定义, 只与直接的朋友沟通
直接的朋友:每个类都会与其它类有耦合关系,耦合方式有 聚合 , 关联 ,组合 等,其中称出现的成员变量, 方法参数, 方法返回值中的类为直接朋友
陌生类最好不要以局部变量的形式出现在类的内部
注意事项和细节
1),迪米特法则的核心是降低类之间的耦合
2),由于每个类都减少了不必要的依赖, 因此迪米特法则只是要求降低类间(对象间耦合关系,并不要求完全没有依赖关系
合成复用原则
尽量使用合成/聚合的方式,而不是使用继承
UML类图
UML(Unified modeling language UML) 统一建模语言
是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果
UML本身是一套符号的规定,就像数学符号和化学符号一样,这样符号用于描述软件模型中的各个元素和他们之间的关系依赖, 泛化(继承) ,实现,关联,组合,聚合与组合
使用UML来建模,idea 使用 plantuml
常用的符号
<|--
表示泛化(继承)
*--
组合
o--
聚合
<|..
表示依赖
--
关联
UML图分类
用例图
静态结构图 : 类图 , 对象图 , 包图 , 组件图 , 部署图
动态行为图 : 交互图 , 状态图 , 活动图
依赖关系: 只要类中用到了对方,那么他们之间就存在依赖关系,如果对象没又对方,连编译都通过不了
1), 类中用到了对方
2), 如果是类的成员属性
3), 如果是方法的返回类型
4),是方法接收的参数类型
5), 方法中使用到
关联关系: 是类与类之间的联系, 他是依赖关联的特例,关联具有导航性, 即双向关联和单向关系 关系具有多样性
聚合关系: 表示整体和部分的关系,整体与部分可以分开, 聚合关系是关联关系的特例,所以它具有关联的导航型与多重性
组合关系: 也是整体与部分的关系, 但是整体与部分不可以在分开
设计模式三种类型
1),创建型模式
单例模式
抽象工厂模式
原型模式
建造者模式
工厂模式
2),结构(类)型模式
适配器模式
桥接模式
装饰模式
组合模式
外观模式
享元模式
代理模式
3),行为(方法)型模式
模板方法模式
命令模式
访问者模式
迭代器模式
观察者模式
中介者模式
备忘录模式
解释器模式
状态模式
策略模式
责任链模式
创建型模式
单例(singleton)模式
类的单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得对象实例的方法(静态方法)
单例模式 8 种写法
饿汉式(静态变量)
优点
写法比较简单,就是在类被装载的时候完成实例化,避免了线程同步问题
缺点
在类状态的时候就完成了初始化,没有达到 Lazy Loading 的效果,如果从开始都没有使用过这个实例,则会造成内存的浪费
饿汉式(静态代码块)
优点和缺点 与静态变量一致,都会造成内存浪费
懒汉式(线程不安全)
优点
起到了 Lazy Loading 加载的效果,但是只能在单线程下使用
缺点
在多线程环境中,一个线程进入了 if(instance == null) 判断语句,还未来的及给对象创建实例,接下来另一个线程也来这个判断语句, 这是就产生了多个实例,所以在多线程下不可以使用这种模式
懒汉式(线程安全,同步方法)
优点
解决了线程不安全的问题
缺点
效率太低了, 每个线程在想获取类的实例的时候,执行 getInstance() 方法都要进行同步,而其实这个方法只执行一次实例化就可以了,后面想要获取该类实例,直接 return 就可以了
懒汉式(线程安全,同步代码块)
是对 同步方法的改进, 但是这样无法解决线程安全的问题,所以生产不推荐使用
双重检查
优点
在线程安全的前提下,有提高了效率,延迟加载推荐使用
静态内部类
优点
采用了类装载机制来保证初始化实例只有一个线程
静态内部类方式在Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance() 方法,才会装载 SingletonInstance 类,从而完成初始化,完成Singleton实例化
避免了线程不安全,利用静态内部类特点实现延迟加载,懒加载 效率高
枚举
优点
枚举实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
JDK展示
Runtime类
注意事项和细节说明
1), 单例模式保证了系统内存中该类只存在一个实例,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2),当想要实例化一个单例类时,必须要记住使用相应获取该类实例的方法,而不是直接new
3),单例模式使用场景为: 需要频繁的进行创建和销毁的对象,创建对象时耗时过多或耗费的资源过多,但又经常用到的对象,工具类对象,频繁访问数据库文件的对象
工厂(factory)模式
简单工厂模式
是由一个工厂对象决定创建出哪一种产品类的实例,简单工厂模式是工厂模式家族最简单实用的模式
定义了一个创建对象的类,由这个类来封装实例化对象的行为
在软件开发中,当我们会用到大量的创建某种,某类或者某批对象时,就会使用到工厂模式
工厂方法模式
定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类
抽象工厂模式
1), 定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2), 抽象工厂模式可以将简单工厂和工厂方法模式进行整合
3),从设计层面上看, 抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
4),将工厂抽象成两层, absFactory(抽象工厂) 和 具体实现的工厂子类,程序员可以根据创建对象类型使用对应的工厂子类,这样将单个的简单工厂变成了工厂簇,更利于代码的维护和扩展
JDK源码工厂模式
Calendar类
小结
1), 将实例化对象的代码提取出来, 放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高项目的扩展和维护性
2),三种工厂模式
3), 设计模式的依赖抽象原则
创建对象实例时, 不要直接new 类,而是把这个new 类的动作放在一个工厂的方法中,并返回
不要让类继承具体类,而是继承抽象类或者实现 interface
不要覆盖基类中已经实现的方法
原型(prototype)模式
克隆羊问题
有一样羊 tom, 年龄为1, 颜色为白色, 请编写与tom 相同的10只样
一般解决思路
优点
比较好理解, 简单易操作
在创建新的对象时,总是需要重新获取原始对象的属性, 如果创建的对象比较复杂时,效率较低
总是需要重新初始化对象,而不是动态获取对象运行时的状态,不够灵活
改进思路
Object 类中的 clone() 该方法可以将一个java 对象复制一份,但是需要实现 clone () 的java类必须实现 一个接口 Cloneable 该接口表示能够复制且具有复制的能力 =>原型模式
工作原理: 通过将一个原型对象传给那个要发动创建的对象, 这个要发动创建的对象的对象通过请求原型对象拷贝他们自己来实例创建,即对象的.clone()
原型模式
用原型实例指定创建对象的种类, 并且通过拷贝这些原型,创建新的对象
原型模式是一种创新型设计模式,允许一个对象在创建另外一个可定制的对象,无需知道如何创建的细节
优点: 让程序具有更高的效率和扩展性
在Spring源码中 bean的作用域 prototype 就用到了原型模式
浅拷贝
1), 对于数据类型是基本数据类型的成员变量, 浅拷贝会直接进行值的传递,也就是将该属性值复制一份给新的对象
2),对于数据类型是引用数据类型的成员变量, 比如说成员变量是某个数组,某个类的对象等, 那么浅拷贝会进行引用传递, 也就是将成员变量的引用值(内存地址)拷贝一份给新的对象, 因为实际上两个对象的该成员变量都指向同一个实例
3),前面的克隆羊就是浅拷贝,浅拷贝默认就是 clone() 方法来实现
深拷贝
1),复制读写的所有基本类型的成员变量值
2), 为所有引用数据类型的成员变量申请存储空间, 并复制每个引用数据类型成员变量所引用的对象, 直到该类对象可达的所有对象, 也就是说, 对象进行深拷贝要对整个对象进行拷贝
实现方式
1), 深拷贝实现方式1--- 重写 clone() 来实现深拷贝
2), 深拷贝实现方式2 ---- 通过对象的序列化来实现深拷贝
注意事项和细节
1),创建新的对象比较复杂时, 可以利用原型模式简化大型的创建过程, 同时也能够提高效率
2),不用重新初始化对象,而是动态地获得对象运行时的状态
3),如果原始对象发生变化(增加或者减少属性),其它克隆对象也会发生相应的变化,无需修改代码
4),在实现深克隆的时候可能需要比较复杂的代码
缺点: 需要为每一个类配置一个克隆方法,这对全新的类来说并不困难,但对已有的类来说,需要修改源代码,违反ocp原则
建造者(Builder)模式
问题: 要建一个房子, 这一流程为 打桩, 砌墙,封顶,各自有各种各样的如 平房,楼房等等
传统方式解决
AbsHouse
CommonHouse
测试类
产生问题? 设计的程序过于简单, 没有设计缓存层对象,程序的扩展和维护不好,也就是说, 这种设计方案, 把产品(房子) 和创建产品的过程(建房子流程) 封装在一起耦合性较强
基本介绍
建造者模式 又叫 生成器模式, 是一种对象构建模式, 他可以将复杂的对象的构建过程抽象出来(抽象类别) 使这个抽象过程的不同实现方法可以构建出不同的表现(属性)的对象
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节
建造者模式
House(产品角色)
AbsHouse(抽象建造者)
CommonHouse(具体建造者)
BuliderDirector(指挥者)
测试类
JDK源码展示 StringBuilder
Appendable 接口定义了多个 append 方法, 即 Appendable 为抽象建造者,定义了抽象方法
AbstractStringBuilder 抽象类实现了 Appendable 接口的方法,这里 AbstractStringBuilder 已经是建造者, 只是不能实例化
StringBuilder 类 即充当了指挥者角色, 同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder抽象类 完成,而 StringBuilder 继承了 AbstractStringBuilder 抽象类
注意事项和细节
1), 客户端(使用程序)不必知道产品内部组成的细节, 将产品本身与产品的创建过程解耦, 使得相同的创建过程可以创建不同的产品对象
2), 每一个具体建造者都相对独立, 而与其它的具体建造者无关, 因此可以很方便地替换具体建造者或者增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
3),可以更加精准地控制产品的创建过程, 将复杂产品的创建步骤分解在不同的方法中,是的创建过程更加清晰,也更方便使用程序来控制创建过程
4),增加新的具体建造者无需修改原有类库的代码,指挥者类针对建造者类编程,系统扩展方便,符合开闭原则
5),建造者模式所创建的产品一般具有较多的共同点, 其组成部分相似, 如果产品之间的差异性能大,则不适合使用建造者模式,因此其使用范围受到一定限制
6), 如果产品的内部变化复杂, 可能会导致需要定义具有很多具体建造者类来实现这种变化, 导致系统变得很庞大, 因此在这种情况下, 要考虑是否使用 建造者模式
抽象工厂 VS 建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品,具有不同分类未付的产品组合,采用抽象工厂模式不需要关心构建过程, 只关心什么产品由什么工厂生产即可
而建造者模式则是要求按照指定蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
结构型模式
基本介绍
将某个类的接口转换成客户期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作
分类三类
类适配器模式
注意事项和细节
1), Java 是单继承机制, 所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口, 有一定局限性
2), src 类的方法在 Adapter 中都会暴露出来, 也增加了使用成本
3), 由于其继承了 src 类, 所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强
对象适配器模式
注意事项和细节
1), 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同,根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性, 也不在要求dst 必须是接口
2),使用成本低, 更灵活
接口适配器模式
基本介绍
1), 当不需要全部实现接口提供的方法时, 可先设计一个抽象类实现接口, 并为该接口中每个方法提供一个默认空实现, 那么该抽象类的子类可有选择的覆盖父类某些方法来实现
2), 适用于一个接口不想使用其它的方法的情况
工作原理
1), 将一个类的接口转换成另一个接口,让原本不兼容的类可以兼容
2), 从用户的角度看不到被适配者,是解耦
3), 用户从调用适配器转化出来的目标接口方法, 适配器再调用被适配者的相关接口方法
4), 用户收到反馈结果,感觉只是和目标接口交互
源码展示 SpringMVC
Spring 定义了一个适配器接口,使得每一种 controller 有一种对应的适配器实现类
适配器代替controller 执行相应方法
扩展controller 时,只需要增加一个适配器类就完成Spring MVC的扩展了
对象适配器---代码展示
V220
AdapterInterfaces
Adapter5V
Phone
Test
桥接(Bridge)设计模式
基本介绍
1), 将实现与抽象放在两个不同的类层次中, 使两个层次可以独立改变
2), 桥接模式基于类的最小设计原则, 通过使用封装,聚合及继承等行为让不同的类承担不同的职责, 它的主要特点是把抽象与行为实现分离开,从而保持各部分的独立性以及应对它们的功能扩展
代码展示
Brand
xiaomi
Phone
qumianping
Test
桥接模式在JDBC的源码
注意事项和细节
1), 实现了抽象和实现部分的分离, 从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统
2), 对于系统的高层部分,需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
3),桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
4),桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
5), 桥接模式要求正确识别出系统中俩个独立变化的维护,因此其使用范围有一定的局限性,即需要有这样的应用场景
应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
JDBC驱动程序
银行转账系统
转账分类: 网上转账 ,柜台转账 , AM机转账
转账用户类型, 普通用户, 银行卡用户, 金卡用户
消息管理
消息类型, 即时消息, 延时消息
消息分类, 手机短信,邮件消息,qq消息
装饰者(Decorator)模式
基本介绍
1), 动态的将新功能附加到对象上, 在对象功能扩展方面,它比继承更有弹性, 装饰者模式也体现了开闭原则
2), 这里提到动态的将新功能添加附加到对象和ocp原则
在JDK源码应用--IO结构,FileInputStream 就是一个装饰者
组合(composite)模式
基本介绍
又叫 部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示整体-部分的层次关系
组合模式以树形结构来组合对象,用来表示部分以及整体层次
组合模式使得用户对单个对象和组合对象的访问具有一致性,即 组合能让客户以一致的方式处理个别对象以及组合对象
解决问题
当我们要处理的对象可以生成一颗树形结构, 而我们要对树上的节点和叶子节点进行操作的时候,它能提供一致的方式,而不用考虑是节点还是叶子
注意事项和细节
1), 简化客户端操作, 客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
2), 具有较强的扩展性, 当我们需要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动
3), 方便创建出复杂的层次关系, 客户端不同理会组合里面的组成细节, 容易添加节点或者叶子从而创建出复杂的树形结构
4),需要遍历组织结构,或者处理的对象具有树形结构时,非常适合使用组合模式
5), 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样, 不适合使用组合模式
外观(facade)模式
基本概念
1), 又叫 过程模式, 外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口, 这个接口使得这一子系统更加容易使用
2), 外观模式通过定义了一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关系这个子系统的内部细节
解决多个复杂接口带来的使用困难,起到简化用户操作的作用
注意事项和细节
1),屏蔽了子系统的细节, 因此外观模式见底了客户端对子系统使用的复杂性
2),外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
3),通过合理的使用外观模式,可以帮我们更好访问的层次
4),当系统需要进行分层设计时,可以考虑使用 外观模式
5), 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口, 让新系统与 Facade 类交互, 提高复用性
6), 不能过多或者不合理的使用外观模式, 使用外观模式好, 还是直接调用模块好, 要以让系统有层次,利于维护的目的
享元(Flyweight)模式
基本介绍
1),也叫蝇量模式,运用共享技术有效地支持大量细粒度的对象
2),常用语系统底层的开发,解决系统的性能问题,像数据库连接池,里面都是创建好的的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重复新创建,如果没有我们需要的则创建一个
3),享元模式能够解决重复对象的内存浪费问题, 当系统有大量相似对象,需要缓冲池时, 不需要总是创建新对象, 可以从缓冲池里拿, 这样可以降低系统内存,同时提高效率
4),享元模式经典的应用场景就是池技术, String 常量池 , 数据库连接池, 缓冲池等等都是享元模式的应用, 享元模式是池技术的重要实现方式
内部状态和外部状态
比如,五子棋,它们都由大量的棋子对象,围棋和五子棋只有黑白两色, 跳棋颜色多一点, 所以棋子颜色就是棋子的内部状态, 而各个棋子之间的差别就是位置不同, 当我们落子后, 落子颜色是定的 , 但位置是变化的, 所以棋子坐标就是棋子的外部状态
1),享元模式提出了两个要求, 细粒度和共享对象, 这里涉及到内部状态和外部状态了, 即将对象分为两个部分 内部状态和外部状态
2), 内部状态 指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
3),外部状态 指对象得以依赖的一个标记,是随着环境的变化而改变的, 不可共享的状态
注意事项和细节
1), 享表示共享 ,元表示对象
2), 系统中有大量的对象,这些对象消耗大量内存, 并且对象的状态大部分可以外部化时,我们可以考虑使用享元模式
3),用唯一标识码判断, 如果内存中有,则返回这个唯一标识码锁标识的对象,用 HashMap 和 HashTable 存储
4), 享元模式大大减少了对象的创建, 减低了程序内存的占用, 提高效率
5), 享元模式提高了系统的复杂度, 需要分离出内部状态和外部状态, 而外部状态具有固化特征, 不应该随着内部状态的变化而变化, 这是我们使用享元模式需要注意的地方
6), 使用享元模式时, 注意划分内部状态和外部状态,并且需要有一个共享类加以控制
7), 享元模式经典的引用场景就是需要缓冲池池的场景, 比如 String 常量池, 数据库连接池
代理(proxy)模式
基本介绍
1), 为目标对象提供一个替身, 以控制对这个对象的访问, 即通过代理对象访问目标对象,这样做的好处是: 可以在目标对象实现的基础上,增强额外的功能操作, 即扩展目标对象的功能
2), 被代理的对象可以是远程对象, 创建开销大的对象或需要完全控制的对象
3),代理模式有不同的形式, 主要有三种, 静态代理 , 动态代理(jdk代理/接口代理) 和 cglib代理(可以在内存中动态创建对象,不需要实现接口)
静态代理
在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
动态代理
1),代理对象,不需要实现接口, 但是目标对象要实现接口, 否则不能用动态代理
2), 代理对象的生成,是利用JDK的api, 动态在内存中构建代理对象
3),动态代理也叫JDK代理,接口代理
cglib代理
静态代理和动态代理都要求目标对象实现一个接口,但有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-cglib代理
cglib 代理也叫作子类代理,它是在内存中创建一个子类对象从而实现对目标对象功能扩展,也有些书把 cglib 归属为动态代理
cglib 是一个强大的高性能的代码生成包,它可以在运行期间扩展java类与实现java接口,它广泛的被许多aop 的框架使用, 例如Spring AOP 实现方法的拦截
在aop编程中如何选择代理模式
目标对象需要实现接口,用jdk代理
目标对象不需要实现接口, 用cglib代理
cglib包底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
常见的代理模式--几种变体
1),防火墙代理
内网通过代理穿透防火墙,实现对公网的访问
2),缓存代理
比如, 当请求图片文件等资源时,先到缓存代理取, 如果去到资源ok,如果没有取到资源,在到公网或者数据库提取,然后缓存
3),远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用,远程代理通过网络和真正的远程对象沟通信息
4),同步代理
主要使用多线程编程中,完成多线程间同步工作
模板(template)方法模式
又叫模板模式, 在一个抽象类公开定义了执行它的方法的模板, 它的子类可以按需重写方法实现,但调用将以抽象类中定义的方式进行
模板模式定义了一个操作中的算法骨架, 而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构, 就可以重新定义该算法的某些特定步骤
模板方法的钩子方法
在模板方法模式的父类中, 我们可以定义一个方法, 它默认不做任何事情,子类可以视情况要不要覆盖它,该方法称为钩子
注意事项和细节
1), 算法只存在于一个地方,也就是在父类中, 容易修改, 需要修改算法时, 只要修改父类的模板方法或者已经实现的某些步骤, 子类就会继承这些修改
2), 实现了最大化代码复用, 父类的模板方法中和已实现的某些步骤会被子类继承而直接使用
3), 即统一了算法,也提供了很大的灵活性, 父类的模板方法确保了算法的结构保持不变,同时由子类来提供部分步骤的实现
4), 该模式的不足之处,每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5), 一般模板方法都加上final 关键字, 防止子类重写模板方法
模板方法模式使用场景
当要完成在某个过程, 该过程要执行一系列步骤, 这一系列步骤基本相同, 但其个别步骤在实现时可能不同, 通常考虑用模板方法模式来处理
命令(command)模式
基本介绍
1),在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道被请求的操作是那个,我们只需要程序运行时指定具体的请求接受者即可, 此时,可以使用命令模式来进行设计
2),命令模式使得请求的发送者与请求接受者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦
3),在命令模式中, 会将一个请求封装为一个对象,以便使用不同的参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作
4),通俗易懂的理解,将军发布命令,士兵去执行,其中又几个角色,将军(命令发布者),士兵(命令执行者),命令(连接将军和士兵)
注意事项和细节
1), 将发起请求的对象与执行请求的对象解耦, 发起请求的对象是调用者, 调用者只要调用命令对象的 execute() 方法就可以让接受者工作,而不必知道具体的接受者对象是谁, 是如何实现的, 命令对象会负责让接受者执行请求的动作, 也就是说, 请求发起者, 和请求执行者之间的解耦是通过命令对象实现的, 命令对象起到了纽带桥梁的作用
2), 容易设计一个命令队列, 只要把命令对象放到队列,就可以多线程的执行命令
3), 容易实现对请求的执行和撤销
4),命令模式的不足时, 可能导致某些系统有过多的具体命令类, 增加了系统的复杂度, 这点在使用中应该注意
5), 空间令也是一种设计模式, 它为我们省去了判空的操作, 在上面实例中, 如果没有空命令, 我们每按下一个键都需要判断空, 这给我们编码带来一定的复杂度
6),命令模式经典应用场景: 界面的一个按钮就是一个命令, 模拟CMD, 订单的撤销/恢复 ,触发-反馈机制
访问者(Visitor)模式
基本介绍
封装了一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用这些数据元素的新的操作
主要将数据结构与数据操作分离,解决了数据结构和操作耦合性问题
访问者模式的基本工作原理是: 在被访问的类里面加一个对外提供接待访问者的接口
应用场景: 需要对一个对象结构中的对象进行很多不同的操作,(这些操作彼此没有关联)同时需要避免这些操作 污染 这些对象的类, 可以选用访问模式来解决
注意事项和细节
优点
1), 访问者模式符合单一职责原则,让程序具有优秀的扩展性,灵活性非常高
2), 访问者模式可以对功能进行统一, 可以做报表, UI , 拦截器与过滤器 ,适用于数据结构相对稳定的系统
缺点
1), 具体元素对访问者公布细节, 也就是说 访问者关注了其它类的内部细节, 这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
2), 违背了依赖倒转原则, 访问者依赖的是具体元素, 而不是抽象元素
使用场景: 因此, 如果一个系统比较稳定的数据结构, 又有经常变化的功能需求, 那么访问者模式就是比较合适的
迭代器(iterator)模式
基本介绍
如果我们的集合元素是用不同的方式实现的, 有数组,还有java 的集合类, 或者还有其它方式, 当客户端要遍历这些集合元素的时候就要使用多种遍历方式, 而且还会暴露元素的内部结构, 可以考虑使用迭代器模式
迭代器模式,提供一种遍历集合元素的统一接口, 用一致的方法遍历集合元素, 不需要知道集合对象的底层表示, 即: 不暴露内部的结构
注意事项和细节
优点
1),提供了一个统一的方法遍历对象,客户不需要考虑聚合的类型, 使用同一种方法就可以遍历对象
2),隐藏聚合的内部结构, 客户端要遍历聚合的时候只能取迭代器, 而不会知道聚合的具体组成
3), 提供了一种设计思想, 就是一个类应该只有一个引起变化的原因(叫 单一职责原则) , 在聚合类中,我们把迭代器分开, 就是把管理打下集合和遍历对象集合的责任分开, 这样一来集合改变的话, 只影响到聚合对象, 而如果遍历方式改变的话, 就影响到迭代器
4), 当要展示一组相似对象, 或者遍历一组相同对象时使用迭代器模式
缺点
每个聚合对象都要一个迭代器, 会生成多个迭代器不好进行管理
观察者(Observer)模式
对象之间多对一依赖的一种设计方案, 被依赖的对象为 Subject ,依赖的对象为 Observer , Subject 通知 Observer 变化, 比如这里的奶站,是 Subject 是1的一方, 用户是 Observer 是多的一方
中介者(mediator)模式
基本介绍
1), 用一个中介对象来封装一系列的对象交互, 中介者使各个对象之间不需要显式的相互调用, 从而使其松耦合,而且可以独立地改变他们之间的交互
2),比如MVC模式,C-控制器是 M-model 模型 和 V-视图的中介者,在前后端交互起到了中间人的作用
注意事项和细节
1), 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦
2), 减少类之间的依赖, 降低耦合, 符合迪米特法则
3), 中介者承担了较多的职责, 一旦中介者出现问题, 整个系统就会受到影响
4),如果涉及不当,中介者对象本身变得过于复杂, 这点在使用时,要特别注意
备忘录(memento)模式
1), 在不破坏封装性的前提下, 捕获一个对象的内部状态, 并再该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态
2), 备忘录对象主要记录一个对象的某种状态, 或者某些数据, 当要回退时, 可以从备忘录对象里获取原来的数据进行恢复操作
注意事项和细节
1), 给用户提供了一种可以恢复状态的机制, 可以使用户能够比较方便地回到某个历史的状态
2), 实现了信息的封装, 使用户不需要关心状态的保存细节
3), 如果类的成员变量过多, 势必会占用比较大的资源, 而且每一次保存都会消耗一定的内存,这个需要注意
4), 适用的场景, 1 打游戏存档, 2 windows 里的 ctrl + z 3, IE中的后退 4,数据库事务管理
5), 为了节省内存,备忘录模式可以和原型模式配合使用
解释器(interpreter)模式
基本介绍
1), 在编译原理中,一个算术表达式通过词法分析器形成词法单元, 而后这些词法单元在通过语法分析器来构建语法分析树,最终形成一颗抽象的语法分析树, 这里的词法分析器和语法分析器都可以看做解释器
2), 给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 使用该解释器来解释语言中的句子
应用场景
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
一些重复出现的问题可以用一种简单的语言来表示
一个简单的语法需要解释的场景
注意事项和细节
1), 当有一个语言需要解释执行, 可将该语言中的句子表示为一个抽象语法树, 就可以考虑使用解释器模式,让程序具有良好的扩展性
2), 编译器, 运算表达式计算, 正则表达式, 机器人 等
3), 使用解释器带来的问题, 解释器模式会引起类膨胀, 解释器模式采用递归调用方法, 将会导致调试非常复杂, 效率可能降低
状态(State)模式
基本介绍
用来解决对象在多种状态转换时, 需要对外输出不同的行为的问题, 状态和行为是一一对应的, 状态之间可以相互转换
当一个对象的内在状态改变时,允许改变其行为, 这个对象看起来像是改变了其类
注意事项和细节
1),代码有很强的可读性,状态模式将每个状态的行为封装到对应的一个类中
2),方便维护,将容易产生问题的if-else 语句删除了, 如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态, 不但会产生很多 if-else 语句, 而且容易出错
3), 符合 开闭原则, 容易增删状态
4), 会产生很多类, 每个状态都要一个对应的类, 当状态过多产生很多类, 加大维护难度
5), 当一个事件或者对象有很多种状态, 状态之间会相互转换, 对不同的状态要求有不同的行为的时候, 可以考虑使用状态模式
策略(strategy)模式
基本介绍
定义算法族,让他们之间可以相互替换,次模式让算法的变化独立于使用算法的客户
这个算法体现了几个设计原则
1), 把变化的代码从不变的代码中分离出来
2), 针对接口编程而不是具体类(定义了策略接口)
3), 多用组合/聚合, 少用继承(客户通过组合方式使用策略)
注意事项和细节
1),策略模式的关键是 分析项目中变化的部分与不变的部分
2),策略模式的核心思想是, 多用组合.聚合 ,少用继承, 用行为类进行封装, 而不是行为继承, 更有弹性
3),体现了 对修改关闭,对扩展开放,原则, 客户端增加行为不用修改原有代码, 只要添加一种策略, 避免了使用 多重转移语句
4),提供了可以替换继承关系的办法, 策略模式将算法封装在独立的 strategy 类中使得你可以独立于其 context 改变它, 使它易于转换, 易于理解, 易于扩展
5), 需要注意的是 ,每添加一个策略就是要增加一个类, 当策略过多是会大破之类数目庞大
责任链(Chain of responsibility)模式
为请求创建一个接收者对象的链, 这样模式对请求的发送者和接收者进行解耦
责任链模式通常每个接收者都包含对领一个接收者引用,如果一个对象不能处理这个请求,那么它会把相同的请求传给下一个接收者,以此类推
注意事项和细节
1), 简化了请求和处理,实现解耦, 提高系统的灵活性
2), 简化了对象,使对象不需要知道链的结构
3), 性能会受影响, 特别是在链比较长的时候, 因此需控制链中最大节点数量, 一般通过在 Handler 中设置一个最大节点数量, 在SetNext 方法中判断是否已经超过了最大的阈值, 超过则不允许该链建立, 避免出现超长链无意识的破坏系统性能
4), 调试不方便, 采用类似递归的方式, 调试时逻辑可能比较复杂
5), 最佳应用场景, 有多个对象可以处理同一个请求时, 比如多级请求, 请假/加薪 , 等审批流程
0 条评论
下一页