参考
https://www.cnblogs.com/pony1223/p/7608955.html
http://www.cyc2018.xyz
面向对象六大原则
单一职责
例如要实现逻辑和界面的分离,一个类只负责一项职责
里氏替换原则
子类可以扩展父类的功能,但不能改变父类原有的功能
依赖倒置原则
要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合
接口隔离原则
建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少
迪米特法则(最少知道原则)
就是一个类对自己依赖的类知道的越少越好。对于被依赖的类来说,无论逻辑多么
复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外
泄漏任何信息
开闭原则
对扩展开放,对修改关闭
设计模式
24种设计模式可以分为三类
- 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程
- 结构型模式:把类或对象结合在一起形成一个更大的结构
- 行为型模式:类和对象如何交互,及划分责任和算法
单例模式
某个类只能有一个实例,提供一个全局的访问点。有三个特点
- 单例类只能有一个实例
- 单例类必须自己创建自己的实例
- 单例类必须提供外界获取这个实例的方法
1 | public class Singleton { |
单例模式之饿汉模式
线程不安全问题主要是由于Instance被实例化多次,采取直接实例化Instance
的方式就不会产生线程不安全问题。不过在类装载的时候就进行了实例化,有可
能这个实例化过程很长,那么就会加大类装载的时间,直接实例化的方式也丢失
了延迟实例化带来的节约资源的好处,没有达到lazy-loading的效果
1 | public class Singleton { |
单例模式之懒汉模式
这种方法达到了lazy-loading 的效果,即我们在第一次需要得到这个单例的时
候,才回去创建它的实例,以后再需要就可以不用创建,直接获取了。但是这种
设计在多线程的情况下是不安全的
1 | //懒汉模式 |
单例模式之懒汉模式(线程安全)
采用同步代码块来达到线程安全。这会让线程阻塞时间过长,因此该方法有性能
问题,不推荐使用
1 | //懒汉模式线程安全 |
单例模式之懒汉模式–双重校验锁
上面的例子我们可以看到,synchronized其实将方法内部的所有语句都已经
包括了,每一个进来的线程都要单独进入同步代码块,判断实例是否存在,
这就造成了性能的浪费。那么我们可以想到,其实在第一次已经创建了实例
的情况下,后面再获取实例的时候,可以不进入这个同步代码块
1 | //懒汉模式线程安全--双重锁校验 |
创建一个变量有两步
- 申请一块内存,调用构造方法进行初始化
- 分配一个指针指向该内存
根据编译器的优化可能会调整指令的顺序,也就是存在这样一种情况:先开辟
一块内存,然后分配一个指针指向该内存,最后调用构造方法进行初始化。
那么针对单例模式的设计,就会存在这样一个问题:线程A开始创建Singleton
的实例,此时线程 B已经调用了getInstance的()方法,首先判断instance
是否为null。而我们上面说的那种模型,A已经把instance指向了那块内存,只
是还没来得及调用构造方法进行初始化,因此B检测到instance不为null,于
是直接把instance返回了。所以应该使用volatile修饰instance
1 | private static volatile Singleton singleton = null; |
我们知道在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器
的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中
修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷
贝,造成数据的不一致。
volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成
员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内
存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值
单例模式之静态内部类
当Singleton类被加载时,静态内部类InnerSingleton没有被加载进内存。只
有当调用getInstance()方法从而触发Inner.instance时Inner 才会被加载
,此时初始化instance实例,并且JVM能确保instance只被实例化一次。这种
方式不仅具有延迟初始化的好处,而且由JVM提供了对线程安全的支持
1 | public class InnerSingleton { |
单例模式之枚举实现
1 | public enum Singleton{ |
单例模式的应用可以分为两类:资源共享和方便资源互相通信
- windows 系统的回收站,我们能在任何盘符删除数据,但是最后都是到了
回收站中 - 网站的计数器,不过不采用单例模式,很难实现同步
- 数据库连接池,可以节省打开或关闭数据库连接所引起的效率损耗,用单例模
式来维护,可以大大降低这种损耗
工厂方法模式
工厂模式是用来创建对象的一种最常用的设计模式。是用工厂方法代替new操作
的一种模式。我们不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中
,那么这个函数就可以被视为一个工厂
Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象
抽象工厂模式
所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需
要明确指定具体类。抽象工厂模式创建的是对象家族,也就是很多对象而不是一个
对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是
用于创建一个对象,这和抽象工厂模式有很大不同
建造者模式
封装一个复杂对象的构建过程,并可以按步骤构造。建造者模式将复杂产品的构建
过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的
控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得
相同的创建过程能够创建不同的产品。StringBuilder的实现就用到该模式
原型模式
使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。在我们应
用程序可能有某些对象的结构比较复杂,但是我们又需要频繁的使用它们,如果
这个时候我们来不断的新建这个对象势必会大大损耗系统内存的,这个时候我们
需要使用原型模式来对这个结构复杂又要频繁使用的对象进行克隆。所以原型模
式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象
1 | public abstract class Prototype { |
适配器模式
将一个类的方法接口转换成客户希望的另外一个接口。在我们的应用程序中我
们可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下我们
可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所
谓适配器模式就是将一个类的接口,转换成客户期望的另一个接口。它可
以让原本两个不兼容的接口能够无缝完成对接
1 | Reader reader = new INputStreamReader(inputStream); |
桥接模式
将抽象部分和它的实现部分分离,使它们都可以独立的变化
组合模式
将对象组合成树形结构以表示“”部分-整体“”的层次结构
装饰模式
动态的给对象添加新的功能,一种动态地往一个类中添加新的行为的设计
、模式。就功能而言,装饰器模式相比生成子类更为灵活,这样可以给某
个对象而不是整个类添加一些功能
1 | new BufferedInputStream(new FileInputStream(inputStream)); |
代理模式
为其他对象提供一个代理以便控制这个对象的访问
亨元模式
通过共享技术来有效的支持大量细粒度的对象
责任链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系
。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止
命令模式
将命令请求封装为一个对象,使得可以用不同的请求来进行参数化
解释器模式
给定一个语言,定义它的文法的一种表示,并定义一个解释器
迭代器设计模式
一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
Collection接口在元素遍历的设计上采用迭代器的设计模式。迭代器给我们
提供了统一的接口来遍历实现了迭代器接口的类的对象,实现了遍历集合方
法的复用,减少我们的代码量
中介者模式
用一个中介对象来封装一系列的对象交互
备忘录模式
在不破坏封装的前提下,保持对象的内部状态
观察者模式
对象间的一对多的依赖关系
状态模式
允许一个对象在其对象内部状态改变时改变它的行为
策略模式
定义一系列算法,把他们封装起来,并且使它们可以相互替换
模板方法设计模式
定义一个算法结构,而将一些步骤延迟到子类实现。
在父类中声明一些必要的抽象方法,同时父类通过这些抽象方法来实现一些
实例方法,而这些个抽象方法通过继承来交给子类实现,子类根据自己的特
性来自定义实现这些抽象方法,以达到最好的执行效率。利用多态性来达到
不同的子类有不同的行为的同时也保证了整个框架具有良好的扩展性。比如
抽象类AbstractCollection中的contains remove等方法都用到了迭代
器,这些实例方法都依赖于两个抽象方法iterator和size
访问者模式
在不改变数据结构的前提下,增加作用于一组对象元素的新功能
空对象模式
使用什么都不做的空对象来代替NULL。一个方法返回 NULL,意味着方法的调
用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码
。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,
那么就有可能抛出空指针异常
责任链模式
将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会
委托的设计思想
SubList类中并没有重新创建一个列表List,而是创建一个字段指向源列表
对象,并且将起始下标(fromIndex)作为偏移量,而之后对子列表元素的
相关操作都是通过调用源列表的相关方法并且算上偏移量(offset)来实
现的
代理模式
我们使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的
前提下,提供额外的功能操作,扩展目标对象的功能。代理模式的主要作用是扩
展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自
定义的操作
静态代理
首先得有一个接口,通用的接口是代理模式实现的基础
1 | public interface Subject |
我们要有一个真正的实现这个接口的类,和一个只是实现接口的代理类。
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能
的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或
者是共同继承某个类
1 | public class RealSubject implements Subject |
可以看到静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现
相同的接口或继承相同的抽象类
动态代理
在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现该接口
的代理,而不需要去定义这个类。这就是它被称为动态的原因
1 | public interface Subject |
编写代理类,需要实现InvocationHandler 接口,重写invoke() 方法
1 | public class ProxyHandler implements InvocationHandler |
通过Proxy 的静态方法newProxyInstance 才会动态创建代理
1 | public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, |
- loader 自然是类加载器
- interfaces 代码要用来代理的接口
- h 一个InvocationHandler 对象
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有
一个与之关联的InvocationHandler 实现类,如果代理的方法被调用,那
么代理便会通知和转发给内部的InvocationHandler 实现类,由它决定处
理。InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决
定了怎么样处理代理传递过来的方法调用
1 | public interface InvocationHandler { |
- proxy 代理对象
- method 代理对象调用的方法
- args 调用的方法中的参数
因为Proxy 动态产生的代理会调用InvocationHandler 实现类,所以该类是
实际执行者
动态代理和静态代理的区别
- 静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现相同
的接口或继承相同的抽象类。静态代理模式一般由业务实现类和业务代理类组成
,业务实现类里面实现主要的业务逻辑,业务代理类负责在业务方法调用的前
后作一些你需要的处理,以实现业务逻辑与业务方法外的功能解耦,减少了对
业务方法的入侵。静态代理又可细分:基于继承的方式和基于聚合的方式实现 - 静态代理模式的代理类,只是实现了特定类的代理,代理类对象的方法越多
,你就得写越多的重复的代码。动态代理就可以动态的生成代理类,实现对不同
类下的不同方法的代理 - JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务
方法前调用InvocationHandler 处理。代理类必须实现 InvocationHandler
接口,并且JDK 动态代理只能代理实现了接口的类
JDK 动态代理的步骤
- 编写需要被代理的类和接口
- 编写代理类,需要实现InvocationHandler 接口,重写invoke() 方法
- 使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[]
interfaces, InvocationHandler h)动态创建代理类对象,通过代理类
对象调用业务方法
静态代理和动态代理的区别?
- 静态代理 为现有的每一个类都编写一个对应的代理类,并且让它实现和
目标类相同的接口(假设都有),在创建代理对象时,通过构造器塞入一个
目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前
后打印日志。也就是说,代理对象=增强代码 +目标对象(原对象)。有了代
理对象后,就不用原对象了,静态代理没有修改原对象的代码 - 动态代理 要创建一个实例,最关键的就是得到对应的Class对象。不写代
理类,而直接得到代理Class对象,然后根据它创建代理实例(反射)。代理
类和目标类理应实现同一组接口。之所以实现相同接口,是为了尽可能保证
代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都
可以转移到目标对象身上,代理对象只需专注于增强代码的编写。接口拥
有代理对象和目标对象共同的类信息。所以我们可以从接口那得到理应由
代理类提供的信息。但是接口是无法创建对象的
静态代理和动态代理的优缺点?
- 静态代理
- 缺点 静态代理的缺点是程序员要手动为每一个目标类编写对应的代理类。
如果当前系统已经有成百上千个类,工作量太大了 - 如果接口增加一个方法,比如 UserService 增加修改 updateUser()
方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此
方法。增加了代码维护的复杂度
- 动态代理
- 缺点 动态代理生成的代理对象,最终都可以用接口接收,和目标对象一
起形成了多态,可以随意切换展示不同的功能。但是切换的同时,只能使
用该接口定义的方法
代理Class对象和代理对象的区别?
代理Class对象是Class类型,代理Class其实就是附有构造器的接口Class,一样
的类结构信息,却能创建实例。而代理对象可以赋值给接口类型,只要实现该接口
就是该类型,代理对象的本质就是:和目标对象实现相同接口的实例。代理Class
可以叫任何名字,只要它实现某个接口,就能成为该接口类型
动态代理的原理?
InvocationHandler 接口和Proxy 类。这两个类相互配合,入口是Proxy
- Proxy 有个静态方法:getProxyClass(ClassLoader,interfaces)
,只要你给它传入类加载器和一组接口,它就给你返回代理Class对象 - getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结
构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创
建对象的 - 一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理
Class,通过代理Class即可创建代理对象 - 根据代理Class的构造器创建对象时,需要传入 InvocationHandler。
代理对象的内部确实有个成员变量invocationHandler,每次调用代理对
象的方法,最终都会调用InvocationHandler的invoke()
方法 - InvocationHandler对象成了代理对象和目标对象的桥梁,不像静态代
理这么直接