23种设计模式
单例模式
饿汉式
(线程安全,调用效率高,但是不能延时加载)
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
// 类初始化时,立即加载这个对象!加载类时,天然时线程安全的。
private HungrySingleton() {
}
// 方法没有同步,调用效率高
public static HungrySingleton getInstance() {
return instance;
}
}
懒汉式
(线程安全,调用效率不高,但是,可以延时加载)
public class LazySingleton {
private static LazySingleton instance;
//类初始化时,不初始化这个对象(延时加载,真正用的时候才去创建)
private LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
双重检测锁式
(由于JVM底层内部模型的原因,偶尔会出现问题,所以不推荐使用)
public class LockSingleton {
private volatile static LockSingleton instance;
private LockSingleton() {
}
public static LockSingleton getInstance() {
if (instance == null) {
synchronized (LockSingleton.class) {
if (instance == null) {
instance = new LockSingleton();
}
}
}
return instance;
}
}
静态内部类式
(线程安全,调用效率高,但是,可以延时加载)
public class InternalSingleton {
private static class SingletonHolder {
private static final InternalSingleton INSTANCE = new InternalSingleton();
}
private InternalSingleton() {
}
public static InternalSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举式
(线程安全,调用效率高,不能延时加载,天然防止反射和序列化)
public enum EnumSingleton {
INSTANCE;
public void doSomeThing() {
}
}
防范反射和反序列化破解
public class SingletonDemo06 implements Serializable {
private static SingletonDemo06 instance;
//类初始化时,不初始化这个对象(延时加载,真正用的时候才去创建)
private SingletonDemo06() {
if (instance != null) { // 用来防止反射
throw new RuntimeException();
}
}
public static synchronized SingletonDemo06 getInstance() {
if(instance == null) {
instance = new SingletonDemo06();
}
return instance;
}
// 反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新的对象!
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
反射和反序列化的破解方法
public class Client {
public static void main(String[] args) throws Exception {
SingletonDemo06 s1 = SingletonDemo06.getInstance();
SingletonDemo06 s2 = SingletonDemo06.getInstance();
System.out.println(s1);
System.out.println(s2);
// 通过反射方式直接调用私有构造器
//Class<SingletonDemo06> clazz = (Class<SingletonDemo06>) Class.forName("gof23.singleton.SingletonDemo06");
//Constructor<SingletonDemo06> c = clazz.getDeclaredConstructor(null);
//c.setAccessible(true); // 解除权限封锁
//SingletonDemo06 s3 = c.newInstance();
//SingletonDemo06 s4 = c.newInstance();
//System.out.println(s3);
//System.out.println(s4);
// 通过反射序列化(Serializable)的方式构造多个对象
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
SingletonDemo06 s3 = (SingletonDemo06)ois.readObject();
ois.close();
System.out.println(s3);
}
}
单例模式的性能
单例模式名称 | 效率测试花费时间 |
---|---|
懒汉式 | 303 |
饿汉式 | 226 |
静态内部类式 | 332 |
枚举式 | 152 |
双重检查锁式 | 162 |
单例对象占用资源少,不需要延时加载:枚举式好于饿汉式
单例对象占用资源大,需要延时加载:静态内部类好于懒汉式
CountDownLatch
- 同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或者多个线程一直等待
- countDown()当前线程调此方法,则计数器减一(建议放在finally里边执行)
- await(),调用此方法会一直阻塞当前线程,直到计时器的值为0。
public class Client2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int threadNum = 100;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
// 设置线程计数器,(由于变量作用域的问题,所以需要声明为final变量)
for (int j = 0; j < threadNum; j++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
Object o = LazySingleton.getInstance();
}
countDownLatch.countDown();
// 线程计数器减一
}
}).start();
}
countDownLatch.await(); // main线程阻塞,直到计数器变为0,才会继续往下执行
// 在这儿要注意时await,不是wait
// 可以在这儿一直检测线程有没有执行完,也就是监测线程是否存活
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start));
}
}
工厂模式
实际中使用简单工厂模式
面向对象设计的基本原则
- OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭;也就是说,不要修改原来的类,创建新的类实现新的功能。
- DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程
- LoD(迪米特法则,Law of Demeter):只与你直接的朋友通讯,而避免和陌生人通信。
简单工厂模式
用来生产统一等级结构中的任意产品。(对于增加新的产品,只需要修改已有的代码)
- 静态工厂模式(别称)
- 虽然某种程度不符合设计原则,但实际使用最多。
public class CarFactory {
public static Car createCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new Byd();
} else {
return null;
}
}
}
工厂方法模式
用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 不修改已有类的前提下,通过增加新的工厂实现拓展
public class Client {
public static void main(String[] args) {
Car c1 = new AudiFactory().createCar();
Car c2 = new BydFactory().createCar();
Car c3 = new BenzFactory().createCar();
c1.run();
c2.run();
c3.run();
}
}
抽象工厂模式
用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品簇)
- 不可以增加产品,可以增加新的产品族!
建造者模式
本质:分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。 由于实现了构建和装配的解耦。不同的构建起,相同的装配,也可以做出不同的对象;相同的构建起,不同的装配顺序也可以做出不同的对象。也就是实现了构造算法、装配算法的解耦,实现了更好的复用。
原型模式(prototype)
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
- 原型模式就是Java中的一种克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象爱过你具备原型对象的特点
- 原型模式的优势,效率高,直接克隆,避免了重新构造过程步骤。
- 克隆类似于new,但是不同与new。new创建新的对象属性采用默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响圆形对象。然后,再修改克隆对象的值。
- 原型模式实现Cloneable接口和clone方法
- Prototype模式中实现起来最困难的地方就是内存赋值操作,所幸再Java中提供了clone()方法替我们做了绝大部分事情。
public class Sheep implements Cloneable, Serializable {
// 序列化和反序列化必须实现Serializable接口,实现复制必须有Cloneable接口
// 浅克隆
// 克隆
private String name;
private Date birthday;
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); // 直接调用Object 对象的克隆方法(clone())
return obj;
}
public Sheep(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
//#############################################
public class Sheep2 implements Cloneable{
// 深克隆
private String name;
private Date birthday;
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); // 直接调用Object 对象的克隆方法(clone())
// 添加如下代码,实现深克隆
Sheep2 s = (Sheep2) obj;
s.birthday = (Date) this.birthday.clone(); // 把属性也进行克隆!
return obj;
}
public Sheep2(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
// ###################################
public class Client3 {
public static void main(String[] args) throws Exception {
// 使用序列化和反序列化实现深度克隆
Date d = new Date(1231414124124L);
Sheep s1 = new Sheep("少理", d);
System.out.println(s1);
System.out.println(s1.getName());
System.out.println(s1.getBirthday());
// Sheep s2 = (Sheep) s1.clone(); // s2的对象是一个新的对象
// 使用序列化和反序列化实现深复制
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(s1);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Sheep s2 = (Sheep) ois.readObject();
System.out.println("修改原型对象的属性值");
d.setTime(1541412451510L);
System.out.println(s1.getBirthday());
s2.setName("多利");
System.out.println(s2);
System.out.println(s2.getName());
System.out.println(s2.getBirthday());
}
}
适配器模式(adapter)
- 适配器模式就是相当于一个高清转化线,通过一个中间节点将两个不能连接在一块儿的东西连到一块儿。
代理模式(proxy)
静态代理模式
- 通过代理来办理不重要的业务,你来办理重要的业务。
- 就比如明星的经纪人,明星主要负责主要的工作,代理负责周改变的事物。
动态代理模式
- 动态代理模式就不是只能代理一个人的了,而是一个公司,给很多艺人提供代理(艺人干的事情大体类似),艺人只需要提供它的姓名,然后公司还是可以做出相应的反应。
public class RealStar implements Star {
@Override
public void confer() {
System.out.println("RealStar.confer");
}
@Override
public void signContract() {
System.out.println("RealStar.signContract");
}
@Override
public void bookTicker() {
System.out.println("RealStar.bookTicker");
}
@Override
public void sing() {
System.out.println("RealStar.sing");
}
@Override
public void collectMoney() {
System.out.println("RealStar.collectMoney");
}
}
interface Star {
void confer(); // 面谈
void signContract(); // 签合同
void bookTicker(); // 订票
void sing(); // 唱歌
void collectMoney(); // 尾款
}
// ###############################
public class StarHandler implements InvocationHandler {
private Star realStar;
public StarHandler(Star realStar) {
this.realStar = realStar;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
System.out.println("真正的方法执行");
System.out.println("面谈,签合同,等等");
if (method.getName().equals("sing")) {
object = method.invoke(realStar, args);
}
System.out.println("处理结束");
return null;
}
}
// ####################################
public class Client {
public static void main(String[] args) {
Star realStar = new RealStar();
StarHandler handler = new StarHandler(realStar);
Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Star.class}, handler);
proxy.sing();
}
}
桥接模式(bridge)
- 桥接模式可以取代多层继承的方案,多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大地减少子类的个数,从而降低管理和维护的成本。
- 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则
- 就比如说电脑:电脑有品牌和类型(dell,lenovo,台式机,笔记本)
- 你只需要让类型实现品牌的接口,然后就可以简单的改变一维,实现全部的改变
- 不用全局改变。
public class Computer2 {
/**
* 这是桥接模式最重要的部分
* 就是在一个类中把另外一个类当成一个属性加入进去
*/
protected Brand brand;
public Computer2(Brand brand) {
this.brand = brand;
}
public void sale() {
brand.sale();
}
}
class Desktop2 extends Computer2 {
public Desktop2(Brand brand) {
super(brand);
}
@Override
public void sale() {
super.sale();
System.out.println("Desktop2.sale");
}
}
class Laptop2 extends Computer2 {
public Laptop2(Brand brand) {
super(brand);
}
@Override
public void sale() {
super.sale();
System.out.println("Laptop2.sale");
}
}
// ###############################
public interface Brand {
void sale();
}
class Lenovo implements Brand{
@Override
public void sale() {
System.out.println("Lenovo.sale");
}
}
class Dell implements Brand {
@Override
public void sale() {
System.out.println("Dell.sale");
}
}
class Shenzhou implements Brand {
@Override
public void sale() {
System.out.println("Shenzhou.sale");
}
}
// ###########################
public static void main(String[] args) {
// 销售联想笔记本电脑
Computer2 c = new Laptop2(new Lenovo());
c.sale();
Computer2 c2 = new Desktop2(new Shenzhou());
c2.sale();
}
组合模式(composite)
- 使用组合模式的场景:
- 把部分和整体的关系哟个属性结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象。
- 组合模式的核心:
- 抽象构建(component)角色:定义了叶子和容器构件的共同点
- 叶子(Leaf)构建角色:无子节点
- 容器(Composite)构建角色:有容器的特征,可以包含子节点
- 就像文件和文件夹的关系,也就是实现了遍历,通过不同的类型处理。
装饰模式(decorator)
- 总结:
- 装饰模式(Decorator)也叫包装器模式(Wrapper)
- 装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新的具体构建类和具体装饰类。
- 优点:
- 扩展对象功能,比继承灵活,不会导致类个数急剧增加
- 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象
- 具体构建类和具体装饰类可以独立变化,用户可以根据自己增加新的具体构建子类和具体装饰子类。
- 缺点:
- 产生很多小对象。大量小对象占据内存,一定程度上影响性能。
- 装饰模式易出错,调试排查比较麻烦
- 装饰器模式就是将功能细分化,就比如说一套餐具,我们使用的时候再组合,这样就可以降低耦合。
外观模式(facade)
- 为子系统提供统一的入口,封装子系统的复杂性,便于客户端调用。
- 比如:我要造汽车,然后我只需要发布这个命令,后面怎么实现由接收你命令的这个人去实现,不用我们来处理。也就是说处理我们命令的那个人就行当与这个外观模式,也就是这个人负责了里边细节的实现。
享元模式(FlyWeight)
- 场景:
- 内存属于稀缺资源,不要随便浪费,如果有多个完全相同或相似的对象,我们可以通过享元模式,节省内存
- 核心:
- 享元模式以共享的方式高效的支持大量细粒度对象的重用。
- 享元对象能做到共享的关键式区分了内部状态和外部状态
- 内部状态:可以共享,不会随环境变化而改变
- 外部状态:不可以共享,会随环境变化而改变
- 享元模式的实现
- FlyweightFactory享元工厂类
- 创建并管理享元对象,享元池一般设计成键值对
- FlyWeight抽象享元类
- 通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
- ConcreteFlyWeight具体享元类
- 为内部状态提供成员变量进行存储
- UnsharedConcreteFlyWeight非共享享元类
- 不能被共享的子类可以设计为非共享享元类
- FlyweightFactory享元工厂类
- 优点:
- 极大减少内存中对象的数量
- 相同或相似对象内存中只有一份,极大的节约资源,提高系统性能
- 外部装填相对独立,不影响内部状态
- 缺点:
- 模式比较复杂,是程序逻辑复杂化
- 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态,使运行时间变长。用时间换取了空间
- 享元模式就相当于,复用相似的内容(同一类的内容),然后把不同的内容(其他类)独立出来,这样就可以减少冗余数据,但是花费时间会增长。
责任链模式
能够将处理同一请求的对象连成一条线
- 责任链模式就相当于我们请假的流程,这个过程就可以连接成一条线,班主任处理不了的,传递给年级主任,年级主任处理不了的传递给主任。。。。,依次套娃。
迭代器模式(iterator)
遍历聚合对象的方式
public class ConcreteMyAggregate {
private List<Object> list = new ArrayList<>();
public void addObject(Object obj) {
this.list.add(obj);
}
public void removeObject(Object obj) {
this.list.remove(obj);
}
public List<Object> getList() {
return list;
}
public void setList(List<Object> list) {
this.list = list;
}
public MyIterator createIterator() {
return new ConcreteIterator();
}
// 使用内部类定义迭代器,可以直接使用外部类的属性
private class ConcreteIterator implements MyIterator {
private int cursor; // 定义游标用于记录遍历时的位置
@Override
public void first() {
cursor = 0;
}
@Override
public void next() {
if (cursor <list.size()) {
cursor++;
}
}
@Override
public boolean hasNext() {
if (cursor < list.size()) {
return true;
}
return false;
}
@Override
public boolean isFirst() {
return cursor == 0 ? true : false;
}
@Override
public boolean isLast() {
return cursor == (list.size() - 1) ? true : false;
}
@Override
public Object getCurrentObj() {
return list.get(cursor);
}
}
}
public interface MyIterator {
void first(); // 将游标指向第一个元素
void next(); // 将游标指向下一个元素
boolean hasNext(); // 判断是否存在下一个元素
boolean isFirst();
boolean isLast();
Object getCurrentObj(); // 获取当前游标指向对象
}
中介者模式(Mediator)
通过一个中间变量来活动,不用产生复杂的网状结构,避免了许多个体之间的关联
- 中介者模式就是将复杂的网络结构,通过中介来简化。
- 就比如说你去注册公司,然后需要跟很多部门打交道,这很麻烦,也很费时间。然而通过中介,你只需要给一些东西,它就可以帮你完成。减少了需要注册公司的人和部门之间的关联。
命令模式(command)
- Command抽象命令类
- ConcreteCommand具体命令类
- Invoker调用者/请求者
- 请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计师确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute(),间接调用接收者的相关操作。
- Receiver接收者
- 接收者执行与请求相关的操作,具体实现对请求的业务处理。
- 未抽象前,实际执行操作内容的对象。
- Client客户类
- 在客户类中需要创建调用者对象、具体命令类对象,在创建具体命令对象时指定对应的接收者。发送者和接收者之间没有直接关系,都通过命令对象间接调用。
- 这个就相当于我拿对讲机给在不同地点的大家发布指令,我只需要发布就行了,不需要找到你。(这玩意有点抽象)
解释器模式(Interpreter)
- 是一种不常用的设计模式
- 当我们需要开发一种新的语言时,可以考虑使用解释器模式
- 尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用Jruby,Groovy,java的js引擎来代替解释器的作用,弥补java语言的不足。
- 开发场景:
- 语法解释器
- 数学正则表达式
访问者模式(Visitor)
- 模式动机:
- 对于存储在一个集合中的对象,他们可能具有不同的类型9即使有一个公公的接口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访问者其访问方式也有所不同。
- 定义:
- 表示一个作用于某结构中的个元素的操作,他是我们可以在不改变元素的类的前提下定义作用于这些元素的新操作。
- 开发场景:
- XML文档解释器
- 编译器的设计
- 复杂集合对象的处理
策略模式(strateqy)
- 场景:
- 不同客户产品的报价(报价策略)
模板方法模式(template method)
方法回调(钩子函数)
- 什么时候使用模板方法模式
- 实现一个算法时,整体步骤很固定。但是,某些部分易变。易变部分可以抽象出来,供子类实现。
状态模式(state)
- 核心:
- 用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题
- 结构:
- Context环境类
- 环境类中维护一个State对象,他是定义了当前的状态
- State抽象状态类
- ConcreteState具体状态类
- 每一个类封装了一个状态对应的行为。
- Context环境类
- 开发场景:
- 银行系统中的帐号状态的管理
- OA系统中公文状态的管理
- 酒店系统中,房间状态的管理
- 线程对象各状态之间的切换。
观察者模式(observer)
javaSE提供了java.util.Observable和java.util.Observer来实现观察者模式
- 观察者模式就相当于一个广播。
- 聊天室程序,一个人将消息发给服务器,服务器转发给所有的客户端
备忘率模式(memento)
- 核心
- 就是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。
- 结构
- 源发器类Originator
- 备忘录类Memento
- 负责人类CareTake