23种设计模式学习笔记【上篇】

1、简单工厂: 选择实现 
Factory去接收客户端的请求,可以根据不同的参数按需返回相关实例。

2、外观模式【Facade】: 封装交互,简化调用。功能的组合调用
外观通常指的类,是客户端和被访问的系统之间的一个通道。外观名义上是对外的public方法。 Facade对象知道各个模块,但各个模块不应该知道Facade对象。目的是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。不给子系统添加新的功能接口,外观应该是包装已有的功能,它主要负责组合已有功能来实现客户需要,而不是添加新的实现。提供了缺省的功能实现的同时,也不限制那些需要更多定制功能的用户,可以直接越过外观去访问内部模块的功能。
可以将Facade类设计为interface,当具体子类实现中既包含系统对外接口又包含系统对内接口时,使用interface可以有效减少对外api访问内部接口,防止接口污染行为。

3、适配器模式【Adapter】:转换匹配,复用功能。进行转换匹配,复用已有的功能,而不是来实现新的接口----把不兼容的接口转换成客户端期望的样子。
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。对于老接口升级新接口的样态。在适配器类构造器类注入需要转换的对象,在实现目标适配方法中适配原方法。
智能适配器:在适配器实现里面,加入新功能的实现。
双向适配器:适配器类去实现新老两个需要适配的接口,分别注入两个操作对象类,新接口使用老接口名称;老接口使用新接口名称。这样通过Adapter类就可以旧的使用新接口可以使用;新的使用旧接口可以使用。提供对所有客户的透明性。尤其在两个不同的客户需要用不同的方式查看同一个对象时,适合使用双向适配器。
对象适配器:依赖于对象组合,采用对象组合的方式。 类适配器: 多重继承对一个接口与另一个接口进行匹配。
实现上:
· 类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。
· 对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理Adaptee的子类了;对于对象适配器,允许一个Adapter和多个Adaptee,包括Adaptee和它所有的子类一起工作。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。
· 对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法;对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。
· 对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee,对于对象适配器,需要额外的引用来间接得到Adaptee。

java不支持多重继承,故java中不能实现标准的类适配器的。 变形--实现Target接口然后继承Adaptee的实现。
-- 更好的复用性+扩展性,但过多地使用适配器,会让系统非常凌乱,不容易整体进行把握。

4、单例模式【Singleton】: 控制实例数目
系统中会同时存在多份配置文件的内容,这样会严重浪费内存资源。让类自身来负责自己类实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法。
懒汉式:在声明初始化变量时默认为null,在getInstance方法调用时候判定变量是否为
null,如果为null,则new一个对象,然后返回,否则直接返回。
饿汉式:私有初始化变量直接创建一个单例实例。getInstance方法直接返回实例对象。

在不加同步的情况下,懒汉式是线程不安全的;饿汉式是线程安全的。
懒汉式在获取实例的方法上增加synchronized的关键字【缺点是降低了运行速度】
改进:双重检查枷锁机制 -- 在变量加关键字volatile , 判断两次instance是否==null , 第二次使用synchronized加锁的方法块。
**注意:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高,也就是虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但是并不建议大量采用。
➡️: 目的实现延迟加载和线程安全。 好的实现机制: Lazy initialization holder class 综合运用类级内部类和多线程缺省同步锁。
类级内部类: 有static修饰的成员式内部类/ 相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,可以直接创建 / 可以定义静态方法,静态方法中只能够引用外部类中的静态成员方法或成员变量 / 相当于其外部类的成员,只有在第一次被使用才会被装载
对象级内部类:没有static修饰的成员式内部类 / 绑定在外部对象实例中的 / 

java jvm隐含地执行了同步的情况:
1、由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2、访问final字段
3、在创建线程之前创建对象时
4、线程可以看见它将要处理的对象时

巧妙的实现方式
java枚举类型的基本思路是通过公有的静态final域为每个枚举常量导出实例的类
从某种角度讲,枚举是单例的泛型化,本质上是单元素的枚举。

5、工厂模式 : 延迟到子类来选择实现 【很好的体现了“依赖倒置原则”】
依赖倒置原则: “要依赖抽象,不要依赖于具体类”。不能让高层组件依赖于低层组件,而且不管高层组件还是低层组件,都应该依赖于抽象
优点:可以在不知具体实现的情况下编程、更容易扩展对象的新版本、连接平行的类层次
缺点:具体产品对象和工厂方法的耦合性
框架的定义: 框架就是能完成一定功能的半成品软件
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类
通常父类会是一个抽象类,里面包含创建所需对象的抽象方法,这些抽象方法就是工厂方法
从某个角度讲,工厂方法模式和Ioc/Di的思想很类似, “主动变被动”、“主从换位”,从而获得了更灵活的程序结构
通过参数来实例化不同的实现类

场景:
1、如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类中实现
2、如果一个类本身就希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式

6、抽象工厂模式: 选择产品簇的实现
区别:工厂方法模式或简单工厂关注的是单个产品对象的创建。 
它们无法解决创建一系列的产品对象,而且这一系列对象是构建的对象所需要的组成部分,也就是这一系列被创建的对象相互之间是有约束的
优点:分离接口和实现、使得切换产品簇变得容易、
缺点:不太容易扩展新的产品、容易造成类层次复杂
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
抽象工厂的功能是为一系列相关对象或相互依赖的对象创建一个接口 (一系列相关或相互依赖的方法)
抽象工厂出方案,具体去挑选不同的方案进行整体的步骤实现

场景:
1、希望一个系统只是知道产品的接口,而不关心实现的时候
2、可以动态地切换产品簇的时候
3、要强调一系列相关产品的接口,以便联合使用它们的时候

抽象工厂模式 当产品簇只有一个产品--> 工厂模式 当把子类实现放到工厂中实现时 --> 简单工厂模式

7、代理模式 : 控制对象的访问
部门编号的设计技巧: 可以使用一个字段装入部门id集合,这样就可以直接使用like,只要找到以该编号开头的所有部门就可以了,不用递归查找了
定义:为其他对象提供一种代理以控制对这个对象的访问。 代理对象夹在客户端和被代理的真实对象中间,相当于一个中转,那么在中转的时候就有很多花招可以玩。
假定场景:客户既想一次性访问多条数据,但不使用分页处理,在性能上考虑又想节省内存的前提下
基本思路:先只拿取用户的编号和姓名,在想查看的用户后再请求详细信息,算时间换空间的策略
定义用户数据对象的接口 UserModelApi
分别使用UserModel和Proxy来实现这个接口作为entity类
proxy对象:不完整的完整对象
proxy对象中引用一个真实对象,使用构造器来初始化注入,同时实现接口抽象方法,在同样实现的方法中实现逻辑并调用真实对象的实现方法
设定一个标志,用来判定是否需要重新加载数据对象,在reload()后,修改标志的正负
对于上述最合适的场景:客户大多数情况只查看基本情况,极少量操作详细数据。这样既节省内存,又减少操作数据库的次数
Hibernate ORM框架 Lazy Load的实现就是用代理思想来实现的。
使用最高的代理:
虚代理:根据需要来创建开销很大的对象,该对象只有在需要的时候才会被真正创建 (上述事例)
保护代理:控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问 (下述事例)
远程代理 --> Java RMI技术

订单代理与订单对象实现一样的接口;要控制对订单setter方法的访问,那么就需要在代理的方法里面进行权限判断,有权限则调用订单对象的方法,没有权限则提示错误并返回

java动态代理:只能代理接口 ,依靠invoke和动态生成class的技术,来动态生成被代理的接口的实现对象。 cglib 

代理两种实现方式: 主要是使用对象的组合和委托、对象继承的方式来实现代理
场景:
1、需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理
2、需要按照需要创建开销很大的对象的时候,可以使用虚代理
3、需要控制对原始对象的访问的时候,可以使用保护代理
4、需要在访问对象执行一些附加操作的时候,可以使用智能指引代理。

8、策略模式 : 相同行为的不同实现 分离算法,选择实现
不使用模式时需要大量使用判断来操作,固定写法不好维护。 多个if-else语句可以考虑使用策略模式
定义:定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
将具体的算法和直接使用算法的客户分离,好处使得算法可独立于使用它的客户而变化,并且能够动态地切换需要使用的算法,只要客户端动态的选择使用不同的算法,然后设置到上下文对象中去,在实际调用的时候,就可以调用到不同的算法。

定义一个策略需要使用的公共接口,然后使各个具体策略的实现类来实现这个策略接口,在公共方法中使用对象注入的方式,来具体调用某个策略来实现的方法,以让具体调用权力转移到客户端来实现。

Stratefy的实现不一定必须使用接口方式,如果多个算法具有公共功能的话, 可以把Strategy实现成为抽象类,然后把多个算法的公共功能实现到Strategy中。
容错机制的运用: 捕获不同异常情况,在上下文中使用策略,在异常中使用不同策略。

优点:
定义一系列算法 实现让这些算法可以相互替换。为这一系列算法定义公共的接口,以约束一系列算法要实现的功能。
避免多重条件语句 算法平等 可以互换 。 使用策略模式能避免这样的多重条件语句。
更好的扩展性 增加新的策略实现类 然后在使用策略的地方选择使用这个新的策略实现就可以了

缺点:
客户必须了解每种策略的不同 必须了解各种策略的功能和不同,才能做出正确的选择,这样也暴露了策略的具体实现
增加了对象数目 每个具体的策略实现都单独封装成为类,如果备选策略很多 那么对象的数目就会很客观
只适合扁平的算法结构 平级 就相当于兄弟算法 而且在运行时刻只有一个算法被使用 使用的时候不能嵌套使用 对于需要组合或者嵌套使用多个算法的情况,可以考虑使用装饰模式,或是变形的职责链或AOP等方式来实现


中间上下文的意义在于 大大减少客户端使用的难度,有了上下文客户端只需要与上下文交互就可以了,这样让整个设计模式更独立、更有整体性,让客户端更简单

场景:
1、出现有许多相关的类 仅仅是行为有差别的情况下 可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法的动态切换
2、出现同一个算法,有很多不同实现的情况下,可以使用策略模式来把这些"不同的实现"实现成为一个算法的类层次
3、需要封装算法中,有与算法相关数据的情况下,可以使用策略模式来避免暴露这些跟算法相关的数据结构
4、出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来原则这些行为的情况下,可以使用策略模式来代替这些条件的语句

9、职责链模式 : 分离职责,动态组合 【是职责链模式的精华】
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之前的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止

优势:动态实现 动态组合流程:如果流程发生了变化,只要重新组合就可以了;如果某个处理的业务功能发生了变化,一个方案是修改该处理的业务功能发生了变化;另一个方案是直接提供一个新的实现,然后在组合流程的时候,用新的实现替换掉旧的实现就可以了

步骤:定义一个所有职责的抽象类外观 ,这个类持有下一个处理请求对象,同时还要定义抽象业务处理方法 。 分别继承实现业务处理方法。在客户端使用时候,分别在下级职责链中装入上级,当业务不满足条件时,自动会调用上级业务实现方法

优点:请求者和接受者松散耦合、动态组合职责
缺点:产生很多细粒度对象(每个职责对象只处理一个方面的功能,要把整个业务处理完,需要很多职责对象的组合,这样会产生大量的细粒度职责对象)、不一定能被处理(注意所有在职责链上的对象不能处理时,给予默认处理)

场景:
1、如果有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时动态确定的
2、请求者与接收者之间的解藕,请求者不需要知道究竟是哪一个接收者对象来处理请求
3、动态构建职责链,动态指定处理一个请求的职责对象集合

10、模版方法模式 : 固定算法骨架

定义: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

按照逻辑书写算法的骨架,将父类无法确定的实现,延迟到具体的子类来实现就可以了

在抽象类父类中定义好了算法的步骤,可以实现一些公共的方法,模版方法模式的功能在于固定算法骨架,而让具体算法实现可扩展。

模版方法还额外提供了一个好处,就是可以控制子类的扩展。因为在父类中定义好了算法的步骤,只是在某几个固定的点才会调用到被子类实现的方法,因此也就只允许在这几个点来扩展功能。这些可以被子类覆盖以扩展功能的方法通常被称为"钩子"方法

通常在:“既要约束子类的行为,又要为子类提供公共功能”的时候使用抽象类

模版类实现的就是不变的方法和算法的骨架,而需要变化的地方,都通过抽象方法,把具体实现延迟到子类中了,而且还通过父类的定义来约束了子类的行为,从而使系统能有更好的复用性和扩展性

模版方法模式的一个目的,就在于让其他类来扩展或具体实现在模版中固定的算法骨架汇总的某些算法步骤。

简单总结模版方法模式的两种实现方式:

1> 继承方式: 抽象方法和具体实现的关系是在编译期间静态决定的,是类级的关系;使用JAVA回调,这个关系是在运行期间动态决定的,是对象级的关系

2> 相对而言,使用回调机制会更灵活,因为java是单继承的,如果使用继承的方式,对于子类而言,今后就不能继承其他对象了,而使用回调,是基于接口的

3> 相对而言,使用继承方式会更简单点,因为父类提供了实现的方法,子类如果不想扩展,那就不用管。如果使用回调机制,回调的接口需要把所有可能被扩展的方法都定义进去,这就导致实现的时候,不管你要不要扩展,都要实现这个方法

模版方法模式的优点是实现代码复用;模版方法模式的缺点是算法骨架不容易升级

何时选用模版方法模式:

1> 需要固定定义算法骨架,实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况。
2> 各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复。
3> 需要控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展。

11、装饰模式 : 动态组合

定义: 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。

在装饰模式的实现中,为了能够实现和原来使用被装饰对象的代码无缝结合,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类中,转调被装饰的对象,在转调的前后添加新的功能,这就实现了给被装饰对象增加功能。

现在在面向对象的设计中,有一条基本的规则就是"尽量使用对象组合,而不是对象继承"来扩展和复用功能。

所谓"对象组合":  假若有一个对象A,实现了一个a1的方法,而C1对象想要来扩展A的功能,给它增加一个C11的方法。对象组合就是自爱C1对象中不再继承A对象,而是去组合使用A对象的实例,通过转调A对象的功能来实现A对象已有的功能。

优势:

1> 首先可以有选择地复用功能,不是所有A的功能都会被复用,在C2中少调用几个A定义的功能就可以了

2> 其次在转调前后,可以实现一些功能处理,而且对于A对象是透明的,也就是A对象并不知道在a1方法处理的时候被追加了功能

3> 还有一个额外的好处,就是可以组合拥有多个对象的功能。

何时创建被组合对象的实例:

直接在属性上创建需要组合的对象;通过外部传入需要组合的对象

装饰模式与AOP

AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任。【事务处理、日志管理、权限控制等】封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP改变了 原来是应用系统主动去调用各个公共模块,现在变成了各个公共模块主动织入回到应用系统。这样一来应用系统就不需要知道公共功能模块,也就是应用系统和公共模块解耦了。

优点:

比继承更灵活;

从为对象添加功能的角度来看,装饰模式比继承更灵活。继承是静态的,而且一旦继承所有子类都有一样的功能。而装饰模式采用把功能分离到每个装饰器当中,然后通过对象组合的方式,在运行时动态地组合功能,每个被装饰的对象最终有哪些功能,是由运行期动态组合的功能来决定的

更容易复用功能;

装饰模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,使实现装饰器变得简单,更重要的是这样有利于装饰器功能的复用,可以给一个对象增加多个同样的装饰器,也可以把一个装饰器用来装饰不同的对象,从而实现复用装饰器的功能。

简化高层定义

装饰模式可以通过组合装饰器的方式,为对象增添任意多的功能。因此在进行高层定义的时候,不用把所有的功能都定义出来,而是定义最基本的就可以了,可以在需要使用的时候,组合相应的装饰器来完成所需的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值