设计模式

Posted 悬崖听风098

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式相关的知识,希望对你有一定的参考价值。

设计模式六大原则
1. 单一职责原则--一个类只负责一个功能
2. 迪米特法则 ---类与类之间不要依赖太多
3. 接口隔离原则--每个类建立专用的接口
4. 里氏替换原则--能扩展,但不要覆盖父类非抽象的方法   --低耦合,减少bug
5. 依赖倒置原则--面向接口,而不是面向实现类
6. 开闭原则--对扩展开放,对修改关闭
 
工厂模式
1.什么是工厂模式   统一管理控制
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向
新创建的对象。实现了创建者和调用者分离,工厂模式分为简单工厂、工厂方法、抽象工厂模式
2.工厂模式好处
利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。
将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
 
简单工厂模式
产品类

 

 

工厂类

 

 

客户类

 

 

抽象工厂模式
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品
产品类

 

 

创建工厂类: 

 

 

客户类

 

 

策略模式
定义
  定义一组算法,将每一个算法封装起来,从而使它们可以相互切换。
特点
  1)一组算法,那就是不同的策略。
2)这组算法都实现了相同的接口或者继承相同的抽象类,所以可以相互切换。

 

 //

 

 

 

 

 

 

单例设计模式(面试重点)
概念:单例对象的类必须保证只有一个实例存在
适用场景: 单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的
场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1. 需要频繁实例化然后销毁的对象。
2. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3. 有状态的工具类对象。
4. 频繁访问数据库或文件的对象。 
饿汉式

 

 

懒汉式

 

 为什么懒汉式是线程安全的:

getInsance方法获取实例是不会多次new,而是从Holder的静态内部类里取的,holder里的singleton实例在运行时常量池中由符号引用转变为直接引用时,静态变量instance实例才会被真正创建,在jvm中调用clinit方法可以自动在多线程环境中加锁和同步,如果一个线程在创建实例的过程中耗时较长,其他线程都会阻塞

双重检查锁(double checked locking)

 

 

为什么要使用volatile指令重排序
volatile指令重排序,在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是
不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:
1. 在单线程环境下不能改变程序运行的结果;
 
2. 存在数据依赖关系的不允许重排序 
uniqueSingleton = new Singleton();
分配内存空间、初始化对象、将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
分配内存空间、将对象指向刚分配的内存空间、初始化对象
在这种情况下,T7时刻线程B对uniqueSingleton的访问,访问的是一个初始化未完成的对象(数据不一致的问题)b读到的是一个未初始化的对象。
使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

 

 
为什么要双重检查null
考虑这样一种情况,就是有两个线程同时到达,即同时调用getInstance() 方法,
此时由于singleTon== null ,所以很明显,两个线程都可以通过第一重的 singleTon== null ,
进入第一重 if语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleTon==
null ,
而另外的一个线程则会在lock 语句的外面等待。
而当第一个线程执行完new SingleTon()语句后,便会退出锁定区域,此时,第二个线程便可以进入
lock 语句块,
此时,如果没有第二重singleTon== null 的话,那么第二个线程还是可以调用 new SingleTon()语
句,
这样第二个线程也会创建一个SingleTon实例,这样也还是违背了单例模式的初衷的,
所以这里必须要使用双重检查锁定。细心的朋友一定会发现,如果我去掉第一重singleton == null ,程序还是可以在多线程下完好的运行
的,
考虑在没有第一重singleton == null 的情况下,
当有两个线程同时到达,此时,由于lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利
执行 new SingleTon(),
当第一个线程退出lock 语句块时, singleTon 这个静态变量已不为 null 了,所以当第二个线程进入
lock 时,
还是会被第二重singleton == null 挡在外面,而无法执行 new Singleton(),
所以在没有第一重singleton == null 的情况下,也是可以实现单例模式的?那么为什么需要第一重
singleton == null呢?
这里就涉及一个性能问题了,因为对于单例模式的话,newSingleTon()只需要执行一次就 OK 了,
而如果没有第一重singleTon == null 的话,每一次有线程进入getInstance()时,均会执行锁定操作
来实现线程同步,
这是非常耗费性能的,而如果我加上第一重singleTon == null 的话,
那么就只有在第一次,也就是singleTton ==null 成立时的情况下执行一次锁定以实现线程同步,
而以后的话,便只要直接返回Singleton 实例就 OK 了而根本无需再进入 lock语句块了,这样就可以解
决由线程同步带来的性能问题了。 
 
单例模式的破坏 

 

 

 

spring中bean的单例
1. 当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该
请求对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对单例状态的修改(体
现为该单例的成员属性),则必须考虑线程同步问题。
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数
据,是非线程安全的。在不同方法调用间不保留任何状态。
无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不
能保存数据,是不变类,是线程安全的。

 

 

 

 

 

 

 

 

controller默认是单例的
不要使用非静态的成员变量,否则会发生数据逻辑混乱。 

 

 

单例对象生命周期
//如果是单例,做如下处理
if(mergedBeanDefinition.isSingleton()){
synchronized(this.singletonCache){
//再次检测单例注册表
sharedInstance=this.singletonCache.get(beanName);
if(sharedInstance==null){
...
try {
//真正创建Bean实例
sharedInstance=createBean(beanName,mergedBeanDefinition,args);
//向单例注册表注册Bean实例
addSingleton(beanName,sharedInstance);
}catch (Exception ex) {
...
}finally{
...
}
}
}
bean=getObjectForSharedInstance(name,sharedInstance);
}
//如果是非单例,即prototpye,每次都要新创建一个Bean实例
//<bean id="date" class="java.util.Date" scope="prototype"/>
else{
bean=createBean(beanName,mergedBeanDefinition,args);
}
}
...
return bean;
}
}
@Controller
public class ScopeTestController {
private int num = 0;
@RequestMapping("/testScope")
public void testScope() {
System.out.println(++num);
}
@RequestMapping("/testScope2")
public void testScope2() {
System.out.println(++num);
}
}
//我们首先访问 http://localhost:8080/testScope,得到的答案是1;
//然后我们再访问 http://localhost:8080/testScope2,得到的答案是 2。出生:容器创建时对象出生。(立即创建)
活着:只要容器在,对象一直活着。
死亡:容器销毁,对象消亡。
总结:单例对象与容器共存亡 。
多例对象生命周期
出生:当我们使用对象时,Spring框架为我们创建对象。(延迟创建)
活着:对象只要在使用过程中就一直活着。
死亡:当对象长时间不用且没有别的对象引用时,由Java的垃圾回收器回收

 

工具类用单例模式还是静态方法
如果没有配置信息的工具类,当然是静态类好,随处调用,不需引用
如果有配置信息的工具类,最好还是使用单例模式吧,这样以后如果有多个数据源
 
命令模式 
1. 背景:当需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪
个,使得请求发送者与请求接收者消解耦
(1)我们须要Client和Receiver同时开发,而且在开发过程中分别须要不停重购,改名
(2)如果我们要求Redo ,Undo等功能
(3)我们须要命令不按照调用执行,而是按照执行时的情况排序,执行
(4)在上边的情况下,我们的接受者有很多,不止一个
1. 模式结构
命令模式包含如下角色:
Command: 抽象命令类
ConcreteCommand: 具体命令类
Invoker: 调用者
Receiver: 接收者
Client:客户类
 
 
代理模式
1.什么是代理模式
通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。
(也就是AO的P微实现)
代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能,
这也和Spring的(面向切面编程)很相似
2.代理模式应用场景
Spring AOP、日志打印、异常处理、事务控制、权限控制等
3.代理的分类
静态代理

 

 

 

 

 

 

 

 

 

 

优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理
类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代
理类就非常臃肿,难以胜任。
jdk动态代理
动态代理解决静态代理中代理类接口过多的问题,通过反射来实现的,借助Java自带的
java.lang.reflflect.Proxy,通过固定的规则生成。
步骤如下:
1. 编写一个委托类的接口,即静态代理的(Subject接口)
2. 实现一个真正的委托类,即静态代理的(RealSubject类)
3. 创建一个动态代理类,实现InvocationHandler接口,并重写该invoke方法
4. 在测试类中,生成动态代理的对象。
第一二步骤,和静态代理一样,第三步:

 

 

创建动态代理的对象 

 

 

cglib动态代理
假设我们有一个没有实现任何接口的类HelloConcrete 

 

 

原理:
CGLIB是一个强大的高性能的代码生成包,底层是通过使用一个小而快的字节码处理框架ASM,它可以
在运行期扩展Java类与实现Java接口,Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展
创建代理对象的几个步骤:
1、生成代理类的二进制字节码文件
2、加载二进制字节码,生成Class对象( 例如使用Class.forName()方法 )
3、通过反射机制获得实例构造,并创建代理类对象
区别
1. jdk动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接
口的匿名类,在调用具体方法前调用InvokeHandler来处理。只能对实现了接口的类生成代理只能
对实现了接口的类生成代理
2. cglib:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处
理。主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用
的是继承,对于fifinal类或方法,是无法继承的。
3. 选择
4. 1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
3. 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之
间转换
 
模板方法模式 

 

 

 

 

适配器模式
意图:将一个类的接口转换成客户希望的另外一个接口
如何解决:继承或依赖(推荐)
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复
使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些
源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在
多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。
4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接
口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果
不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至
多只能适配一个适配者类,而且目标类必须是抽象类。
装饰器模式
装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可
以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。 

 

 

 

 

观察者模式
意图:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费
很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调
用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变
化的,而仅仅只是知道观察目标发生了变化。 

 

 

 

 

 

 

以上是关于设计模式的主要内容,如果未能解决你的问题,请参考以下文章

C#设计模式--桥接模式

23种设计模式总结

iOS中都有啥设计模式?各个设计模式的作用

86 设计模式23种设计模式概述

设计模式概述(23种设计模式目录)

设计模式