软件设计原则有哪些?
常⽤的⾯向对象设计原则包括
7
个,这些原则并不是孤⽴存在的,它们相互
依赖,相互补充。
开闭原则(
Open Closed Principle
,
OCP
)
单⼀职责原则(
Single Responsibility Principle, SRP
)
⾥⽒替换原则(
Liskov Substitution Principle
,
LSP
)
依赖倒置原则(
Dependency Inversion Principle
,
DIP
)
接⼝隔离原则(
Interface Segregation Principle
,
ISP
)
合成
/
聚合复⽤原则(
Composite/Aggregate Reuse Principle
,
C/ARP
)
最少知识原则(
Least Knowledge Principle
,
LKP
)或者迪⽶特法则
(
Law of Demeter
,
LOD
)

设计模式的分类了解吗
?
创建型:
在创建对象的同时隐藏创建逻辑,不使⽤
new
直接实例化对
象,程序在判断需要创建哪些对象时更灵活。包括⼯⼚
/
抽象⼯⼚
/
单例
/
建造者
/
原型模式。
结构型:
通过类和接⼝间的继承和引⽤实现创建复杂结构的对象。包
括适配器
/
桥接模式
/
过滤器
/
组合
/
装饰器
/
外观
/
享元
/
代理模式。
⾏为型:
通过类之间不同通信⽅式实现不同⾏为。包括责任链
/
命名
/
解
释器
/
迭代器
/
中介者
/
备忘录
/
观察者
/
状态
/
策略
/
模板
/
访问者模式。
适⽤于⼯⼚类负责创建对象较少的情况,缺点是如果要增加新产品,就需
要修改⼯⼚类的判断逻辑,违背开闭原则,且产品多的话会使⼯⼚类⽐较
复杂。
Calendar
抽象类的
getInstance
⽅法,调⽤
createCalendar
⽅法根据不同
的地区参数创建不同的⽇历对象。
Spring
中的
BeanFactory
使⽤简单⼯⼚模式,根据传⼊⼀个唯⼀的标识来
获得
Bean
对象。
也就是定义⼀个抽象⼯⼚,其定义了产品的⽣产接⼝,但不负责具体的产
品,将⽣产任务交给不同的派⽣类⼯⼚。这样不⽤通过指定类型来创建对
象了。
抽象⼯⼚模式了解吗?
简单⼯⼚模式和⼯⼚⽅法模式不管⼯⼚怎么拆分抽象,都只是针对⼀类产
品,如果要⽣成另⼀种产品,就⽐较难办了!
抽象⼯⼚模式通过在
AbstarctFactory
中增加创建产品的接⼝,并在具体⼦
⼯⼚中实现新加产品的创建,当然前提是⼦⼯⼚⽀持⽣产该产品。否则继
承的这个接⼝可以什么也不⼲。
什么是单例模式?单例模式的特点是什么?
单例模式属于创建型模式,⼀个单例类在任何情况下都只存在⼀个实例,
构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀
个静态公有⽅法获取实例。
优点是内存中只有⼀个实例,减少了开销,尤其是频繁创建和销毁实例的
情况下并且可以避免对资源的多重占⽤。缺点是没有抽象层,难以扩展,
与单⼀职责原则冲突。
单例模式的常⻅写法有哪些?
饿汉式,线程安全
饿汉式单例模式,顾名思义,类⼀加载就创建对象,这种⽅式⽐较常⽤,
但容易产⽣垃圾对象,浪费内存空间。
优点:线程安全,没有加锁,执⾏效率较⾼
缺点:不是懒加载,类加载时就初始化,浪费内存空间
懒加载 (
lazy loading
):使⽤的时候再创建对象
饿汉式单例是如何保证线程安全的呢?它是基于类加载机制避免了多线程
的同步问题,但是如果类被不同的类加载器加载就会创建不同的实例。
代码实现,以及使⽤反射破坏单例:
优点:懒加载,线程安全,效率较⾼
缺点:实现较复杂
这⾥的双重检查是指两次⾮空判断,锁指的是
synchronized
加锁,为什么
要进⾏双重判断,其实很简单,第⼀重判断,如果实例已经存在,那么就
不再需要进⾏同步操作,⽽是直接返回这个实例,如果没有创建,才会进
⼊同步块,同步块的⽬的与之前相同,⽬的是为了防⽌有多个线程同时调
⽤时,导致⽣成多个实例,有了同步块,每次只能有⼀个线程调⽤访问同
步块内容,当第⼀个抢到锁的调⽤获取了实例之后,这个实例就会被创
建,之后的所有调⽤都不会进⼊同步块,直接在第⼀重判断就返回了单
例。
关于内部的第⼆重空判断的作⽤,当多个线程⼀起到达锁位置时,进⾏锁
竞争,其中⼀个线程获取锁,如果是第⼀次进⼊则为
null
,会进⾏单例对
象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返
回已创建的单例对象。
其中最关键的⼀个点就是
volatile
关键字的使⽤,关于
volatile
的详细介
绍可以直接搜索
volatile
关键字即可,有很多写的⾮常好的⽂章,这⾥不做
详细介绍,简单说明⼀下,双重检查锁中使⽤
volatile
的两个重要特性:
可⻅性、禁⽌指令重排序
这⾥为什么要使⽤
volatile
?
这是因为
new
关键字创建对象不是原⼦操作,创建⼀个对象会经历下⾯
的步骤:
1.
在堆内存开辟内存空间
2.
调⽤构造⽅法,初始化对象
3.
引⽤变量指向堆内存空间
对应字节码指令如下:
为了提⾼性能,编译器和处理器常常会对既定的代码执⾏顺序进⾏指令重
排序,从源码到最终执⾏指令会经历如下流程:
源码编译器优化重排序指令级并⾏重排序内存系统重排序最终执⾏指令序
列
所以经过指令重排序之后,创建对象的执⾏顺序可能为
1 2 3
或者
1 3
2
,因此当某个线程在乱序运⾏
1 3 2
指令的时候,引⽤变量指向堆内存
空间,这个对象不为
null
,但是没有初始化,其他线程有可能这个时候进
⼊了
getInstance
的第⼀个
if(instance == null)
判断不为
nulll
,导致错误使
⽤了没有初始化的⾮
null
实例,这样的话就会出现异常,这个就是著名的
DCL
失效问题。
当我们在引⽤变量上⾯添加
volatile
关键字以后,会通过在创建对象指令
的前后添加内存屏障来禁⽌指令重排序,就可以避免这个问题,⽽且对
volatile
修饰的变量的修改对其他任何线程都是可⻅的。
静态内部类
代码实现如下:

代理模式(
proxy pattern
)
什么是代理模式?
代理模式的本质是⼀个中间件,主要⽬的是解耦合服务提供者和使⽤者。
使⽤者通过代理间接的访问服务提供者,便于后者的封装和控制。是⼀种
结构性模式。
下⾯是
GoF
介绍典型的代理模式
UML
类图

Subject:
定义
RealSubject
对外的接⼝,且这些接⼝必须被
Proxy
实现,
这样外部调⽤
proxy
的接⼝最终都被转化为对
realsubject
的调⽤。
RealSubject:
真正的⽬标对象。
Proxy:
⽬标对象的代理,负责控制和管理⽬标对象,并间接地传递外部对
⽬标对象的访问。
Remote Proxy:
对本地的请求以及参数进⾏序列化,向远程对象发送请
求,并对响应结果进⾏反序列化,将最终结果反馈给调⽤者;
Virtual Proxy:
当⽬标对象的创建开销⽐较⼤的时候,可以使⽤延迟或者
异步的⽅式创建⽬标对象;
Protection Proxy:
细化对⽬标对象访问权限的控制;
静态代理和动态代理的区别
1.
灵活性
:动态代理更加灵活,不需要必须实现接⼝,可以直接代理实
现类,并且可以不需要针对每个⽬标类都创建⼀个代理类。另外,静态
代理中,接⼝⼀旦新增加⽅法,⽬标对象和代理对象都要进⾏修改,这
是⾮常麻烦的!
2.
JVM
层⾯
:静态代理在编译时就将接⼝、实现类、代理类这些都变成
了⼀个个实际的
class
⽂件。⽽动态代理是在运⾏时动态⽣成类字节
码,并加载到
JVM
中的。
观察者模式
说⼀说观察者模式
观察者模式主要⽤于处理对象间的⼀对多的关系,是⼀种对象⾏为模式。
该模式的实际应⽤场景⽐较容易确认,当⼀个对象状态发⽣变化时,所有
该对象的关注者均能收到状态变化通知,以进⾏相应的处理。
下⾯是
GoF
介绍的典型的类观察者模式的
UML
类图:

Subject:
抽象被观察者,仅提供注册和删除观察者对象的接⼝声明。
ConcreteSubject:
具体被观察者对象,该对象中收集了所有需要被通知的
观察者,并可以动态的增删集合中的观察者。当其状态发⽣变化时会通知
所有观察者对象。
Observer:
抽象观察者,为所有观察者定义获得通知的统⼀接⼝;
ConcreteObserver:
观察者对象,其关注对象为
Subject
,能接受
Subject
变化时发出的通知并更新⾃身状态。
观察者模式的优缺点
优点:
1.
被观察者和观察者之间是抽象耦合的;
2.
耦合度较低,两者之间的关联仅仅在于消息的通知;
3.
被观察者⽆需关⼼他的观察者;
4.
⽀持⼴播通信;
缺点:
1.
观察者只知道被观察对象发⽣了变化,但不知变化的过程和缘由;
2.
观察者同时也可能是被观察者,消息传递的链路可能会过⻓,完成所有
通知花费时间较多;
3.
如果观察者和被观察者之间产⽣循环依赖,或者消息传递链路形成闭
环,会导致⽆限循环;
你的项⽬是怎么⽤的观察者模式?
在⽀付场景下,⽤户购买⼀件商品,当⽀付成功之后三⽅会回调⾃身,在
这个时候系统可能会有很多需要执⾏的逻辑(如:更新订单状态,发送邮
件通知,赠送礼品
…
),这些逻辑之间并没有强耦合,因此天然适合使⽤
观察者模式去实现这些功能,当有更多的操作时,只需要添加新的观察者
就能实现,完美实现了对修改关闭,对扩展开放的开闭原则。
装饰器模式
什么是装饰器模式?
装饰器模式主要对现有的类对象进⾏包裹和封装,以期望在不改变类对象
及其类定义的情况下,为对象添加额外功能。是⼀种对象结构型模式。需
要注意的是,该过程是通过调⽤被包裹之后的对象完成功能添加的,⽽不
是直接修改现有对象的⾏为,相当于增加了中间层。
下⾯是
GoF
介绍的典型的装饰器模式的
UML
类图:

Component:
对象的接⼝类,定义装饰对象和被装饰对象的共同接⼝;
ConcreteComponent:
被装饰对象的类定义;
Decorator:
装饰对象的抽象类,持有⼀个具体的被修饰对象,并实现接⼝
类继承的公共接⼝;
ConcreteDecorator:
具体的装饰器,负责往被装饰对象添加额外的功
能;
讲讲装饰器模式的应⽤场景
如果你希望在⽆需修改代码的情况下即可使⽤对象, 且希望在运⾏时为对
象新增额外的⾏为, 可以使⽤装饰模式。
装饰能将业务逻辑组织为层次结构, 你可为各层创建⼀个装饰, 在运⾏时
将各种不同逻辑组合成对象。 由于这些对象都遵循通⽤接⼝, 客户端代码
能以相同的⽅式使⽤这些对象。
如果⽤继承来扩展对象⾏为的⽅案难以实现或者根本不可⾏, 你可以使⽤
该模式。
许多编程语⾔使⽤
final
最终关键字来限制对某个类的进⼀步扩展。 复⽤
最终类已有⾏为的唯⼀⽅法是使⽤装饰模式: ⽤封装器对其进⾏封装。
责任链模式
什么是责任链模式?
⼀个请求沿着⼀条
“
链
”
传递,直到该
“
链
”
上的某个处理者处理它为⽌。
⼀个请求可以被多个处理者处理或处理者未明确指定时。
责任链模式⾮常简单异常好理解,相信我它⽐单例模式还简单易懂,其应
⽤也⼏乎⽆所不在,甚⾄可以这么说
,
从你敲代码的第⼀天起你就不知不觉
⽤过了它最原始的裸体结构:
switch-case
语句。
讲讲责任链模式的应⽤场景
当程序需要使⽤不同⽅式处理不同种类请求, ⽽且请求类型和顺序预
先未知时, 可以使⽤责任链模式。该模式能将多个处理者连接成⼀条
链。 接收到请求后, 它会
“
询问
”
每个处理者是否能够对其进⾏处理。
这样所有处理者都有机会来处理请求。
当必须按顺序执⾏多个处理者时, 可以使⽤该模式。 ⽆论你以何种顺
序将处理者连接成⼀条链, 所有请求都会严格按照顺序通过链上的处
理者。
策略模式
什么是策略模式?
策略模式(
Strategy Pattern
)属于对象的⾏为模式。其⽤意是针对⼀组算
法,将每⼀个算法封装到具有共同接⼝的独⽴的类中,从⽽使得它们可以
相互替换。策略模式使得算法可以在不影响到客户端的情况下发⽣变化。
其主要⽬的是通过定义相似的算法,替换
if else
语句写法,并且可以随时
相互替换。
策略模式有什么好处?
定义了⼀系列封装了算法、⾏为的对象,他们可以相互替换。
举例:
Java.util.List
就是定义了⼀个增(
add
)、删(
remove
)、改
(
set
)、查(
indexOf
)策略,⾄于实现这个策略的
ArrayList
、
LinkedList
等类,只是在具体实现时采⽤了不同的算法。但因
为它们策略⼀样,不考虑速度的情况下,使⽤时完全可以互相替换使⽤。
Spring
使⽤了哪些设计模式?
Spring
框架中⽤到了哪些设计模式?
⼯⼚设计模式
: Spring
使⽤⼯⼚模式通过
BeanFactory
、
ApplicationContext
创建
bean
对象。
代理设计模式
: Spring AOP
功能的实现。
单例设计模式
: Spring
中的
Bean
默认都是单例的。
模板⽅法模式
: Spring
中
jdbcTemplate
、
hibernateTemplate
等以
Template
结尾的对数据库操作的类,它们就使⽤到了模板模式。
包装器设计模式
:
我们的项⽬需要连接多个数据库,⽽且不同的客户在
每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据
客户的需求能够动态切换不同的数据源。
观察者模式
:
Spring
事件驱动模型就是观察者模式很经典的⼀个应⽤。
适配器模式
:Spring AOP
的增强或通知
(Advice)
使⽤到了适配器模式、
spring MVC
中也是⽤到了适配器模式适配
Controller
。