工作几年设计模式那些事~
作者:互联网
工作几年设计模式那些事~
- 背景
- Java设计模式种类
- 单例
- 策略模式
- 工厂和抽象工厂
- 门面Facade 和 调停者Mediator
- 观察者Observer
- 装饰者Decorator
- 责任链模式(Chain of responsibility)
- 组合(Composite)
- 享元(Flyweight)
- 代理(Proxy)
- 迭代器(Iterator)
- 访问者(Visitor)
- 构建器(Builder)
- 适配器(Adapter)
- 桥接(Bridge)
- 命令(Command/Action/Transaction)
- 原型/克隆(Prototype)
- 备忘录(Memento)
- TemplateMethod(模板方法/钩子函数)
- State(状态)
- Intepreter(解释器)
- List item
背景
工作几年, 随着经验的积累, 慢慢地开始往自己的代码中添加合适的设计模式, 所写代码也不再是为了单纯完成某个需求, 而是在原先基础上用更少的代码完成更多的事 , 让自己的代码更加地优雅 . 更加健壮的运行 , 下面是一些工作中用到的设计模式及读书或者博文的一些总结.
Java设计模式种类
Java语言中 , 共有23种设计模式,分别是: 单例, 策略 , 工厂和抽象工厂 , 门面Facade , 调停者Mediator , 观察者Observer , 装饰者Decorator , 责任链模式(Chain of responsibility), 组合(Composite) , 享元(Flyweight) , 代理(Proxy),迭代器(Iterator), 访问者(Visitor), 构建器(Builder) , 适配器(Adapter), 桥接(Bridge), 命令(Command/Action/Transaction), 原型/克隆(Prototype), 备忘录(Memento), TemplateMethod(模板方法/钩子函数) , State(状态), Intepreter(解释器)
单例
1.单例类的目的在于确保某一个类只有一个实例对象
2.优点是节约资源、提高效率,同时严格控制客户对它的访问。但是就导致了单例类的职责过重,违反单一职责原则
单例模式常见的写法有八种:
1.恶汉式加载
public class Instance01 {
// 创建单例
private static final Instance01 INSTANCE =
new Instance01();
// 私有构造,保证单例
private Instance01() {};
// 提供单例的方法
public static Instance01 getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Instance01 m1 = Instance01.getInstance();
Instance01 m2 = Instance01.getInstance();
System.out.println(m1 == m2); // true
}
}
2.静态代码块,类似于方法1
public class Instance02 {
private static final Instance02 INSTANCE;
static {
INSTANCE = new Instance02();
}
private Instance02() {};
public static Instance02 getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Instance02 m1 = Instance02.getInstance();
Instance02 m2 = Instance02.getInstance();
System.out.println(m1 == m2);// true
}
}
3.懒汉式加载
public class Instance03 {
private static Instance03 INSTANCE;
private Instance03() {
}
public static Instance03 getInstance() {
if (INSTANCE == null) {// (1)
INSTANCE = new Instance03();
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->
System.out.println(Instance03.getInstance().hashCode())
).start();
}
}
}
输出结果如下:
995327769
1866897138
1866897138
1866897138
770902836
2118558849
原因:多线程方法时候,(1)位置会出现多个多个线程都判断为空的情况,往下执行会new 出多个对象
4.懒汉式(synchronized)
public class Instance04 {
private static Instance04 INSTANCE;
private Instance04() {
}
public static synchronized Instance04 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Instance04();
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Instance04.getInstance().hashCode());
}).start();
}
}
}
上述方法会创建唯一一个对象但是加锁模式下会影响方法执行效率
5.懒汉式(synchronized), 细粒化
public class Instance05 {
private static Instance05 INSTANCE;
private Instance05() {
}
public static Instance05 getInstance() {
if (INSTANCE == null) {
synchronized (Instance05.class) {
INSTANCE = new Instance05();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Instance05.getInstance().hashCode());
}).start();
}
}
}
上述方法虽然与方法4相比synchronized细粒化,但是仍然会出现方法3的多线程并发问题
6.懒汉式(synchronized), 细粒化,双重检查
public class Instance06 {
private static volatile Instance06 INSTANCE;
private Instance06() {
}
public static Instance06 getInstance() {
if (INSTANCE == null) {
synchronized (Instance06.class) {
if(INSTANCE == null) {
INSTANCE = new Instance06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Instance06.getInstance().hashCode());
}).start();
}
}
}
在方法5的基础上多加了个if判断,不会出现多个实例的问题,但是synchronized同样会影响效率
7.静态内部类
public class Instance07 {
private Instance07() {
}
private static class Mgr07Holder {
private final static Instance07 INSTANCE = new Instance07();
}
public static Instance07 getInstance() {
return Mgr07Holder.INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Instance07.getInstance().hashCode());
}).start();
}
}
}
静态内部类的加载只有在getInstance方法被调用的时候才会被类加载,实现了懒汉式,同时应用了方法1,在实现单例的基础上同时又不失效率
8.枚举单例
public enum Instance08 {
INSTANCE;
public void m() {}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Instance08.INSTANCE.hashCode());
}).start();
}
}
}
项目开发之初,都会定义一个错误码表,在项目中,会把这些错误码放到一个统一的枚举类中,如果要是多线程情况下会产生枚举值乱的情况,那项目岂不是乱套了~~~~~~~~~
策略模式
简单的来讲,在程序中策略模式就是实现某一个功能的具体方式, 策略模式巧妙的运用了开闭原则,例如:针对苹果的比较 , 可以有大小,品相,甜度等等 ,没一个比较方式就是一个策略,我们可以使用Comparator接口为每一个比较方式提供出来比较类
策略模式图如下:
工厂和抽象工厂
工厂,顾名思义,在工厂中可以生产各种物件,食物 , 相等在java语言中工厂则是提供某个类,该类中提供了一些对象的创建方法,调用该方法可以创建某些对象
1.简单工厂
简单工厂实现过程为非抽象类,扩展性不好
public class SimpleFactory {
public SmallCar createCar() {
return new SmallCar();
}
}
class SmallCar{
}
以上代码块如果我想创建一个MiddleCar或者BigCar之能对原先的代码进行修改,违反了开闭原则
2.抽象工厂
是对简单工厂的升级:
/**
* 抽象工厂类
* 其中Food等都是抽象方法,具体的实现逻辑用户可以自己做
*/
public abstract class AbastractFactory {
abstract Food createFood();
abstract Vehicle createVehicle();
abstract Weapon createWeapon();
}
类图如下:
门面Facade 和 调停者Mediator
门面:对外而言
类似于行政大厅的办理窗口,对于政府内部材料办公流程全然不知,我们只需在办理窗口提交相关材料即可
调停者:对内而言
可以理解为系统内部协调处理的中间人,典型案例:消息中间件
观察者Observer
习惯性的把观察者理解为偷窥狂,特务 ; 因为观察者是根据其他代码的执行情况做出的一系列反应(观察者根据被观察者的事件做出的反应),比如sptingboot 整合kafka的listener注解就是根据kafka中的消息进行的操作,常见的观察者距离还有hook函数,callback回调函数,handle,listener,spring aop切面,js点击事件,鼠标浮动事件…
类图如下:
装饰者Decorator
装修工,在原来的基础上进行一系列操作,亦或是使代码执行效率更快等等
举个例子:
“小姑娘化妆”
Person -> 上衣,下衣,鞋子,裙子化妆方式多种多样,怎么样实现
让上衣,下衣,鞋子,继承统一的最高类,Person在调用化妆方法的时候传入这个最高类的对象,就可以任意搭配
具体类图如下:
责任链模式(Chain of responsibility)
举个例子:某个人来到行政大厅办理身份证,就需要经过该信息录入->审核->制卡->快递一列流程,如果把其中的每个点看成一个责任点,那么整个流程就是一个责任链
java中的doFilter就是典型的责任链处理流程
组合(Composite)
组合模式用来将多个对象形成树形结构以表示“整体-部分”的结构层次,树形结构专用
享元(Flyweight)
顾名思义:元素共享,java的线程池,数据库连接池,以及jvm中的运行时常量池中的元素用的就是享元思想
代理(Proxy)
代理模式在与分离代理行为和被代理对象,替被代理对象干某件事
java中常见的代理有JDK动态代理和Cglib代理
1.JDK动态代理
Movable a = (Movable)Proxy.newProxyInstance(
// 接口实现类的类加载器
Car.class.getClassLoader(),
// 实现的接口数组
new Class[]{Movable.class},
// 具体处理
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// do something
return null;
}
}
);
a.move();
使用System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,“true”);把产生的代理类save下来
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void move() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
执行原理:在最初代码块中执行Proxy.newProxyInstance会产生代理类的对象,产生对象势必会调用代理类的构造方法,
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
会将第一段代码中InvocationHandler 的实现在Proxy中初始化, 当调用
a.move时,因为产生的是 $Proxy0的对象,所以会调用 $Proxy0的move方法
执行以下代码:
super.h.invoke(this, m3, (Object[])null);
因为之前h已经被Proxy初始化所以这个时候的invoke方法执行的是自己实现的InvocationHandler invoke方法中的逻辑
2.Cglib代理
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Car.class);
enhancer.setCallback(new TimeMethodInterceptor());
Car car = (Car)enhancer.create();
car.move();
}
}
class TimeMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// do something
}
}
缺点:被代理的类Car不能是final 否则执行会报错
迭代器(Iterator)
一般用于容器的遍历,提供了一种游走于各个元素之间的遍历方式,
访问者(Visitor)
访问者模式适用于那些结构不变的对象 , 目的是封装一些针对某些数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变
构建器(Builder)
可以将一个属性特别多的对象的构建过程简单化,假如一个对象拥有100个属性但是其中只有3个真实需要的属性,但是每次在创建这个对象的时候都需要传入剩余97个属性的值,很明显这种方法是不合理的,这时候我们就可以用到构建器,每次创建对象的时候只传必要的属性,灵活可变,如下:
public class Person {
int id;
String name;
int age;
double weight;
int score;
private Person() {}
public static class PersonBuilder {
Person p = new Person();
public PersonBuilder basicInfo(int id, String name, int age) {
p.id = id;
p.name = name;
p.age = age;
return this;
}
public PersonBuilder weight(double weight) {
p.weight = weight;
return this;
}
public PersonBuilder score(int score) {
p.score = score;
return this;
}
public Person build() {
return p;
}
}
}
适配器(Adapter)
我理解的适配器是在某些不兼容场景下, 需要有一个中间处理的环节 ,这个环节具体化就是一个适配器,例如:
1.Java流操作InputStreamReader 和 OutputStreamWriter
public static void main(String[] args) throws Exception {
File f = new File("c:/work/test.data");
FileOutputStream fos = new FileOutputStream(f);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("http://www.mashibing.com");
bw.flush();
bw.close();
}
2.java连接mysql的jar包是mysql-connector-java, 连接sqlserver的包是odbc 但是odbc是不能访问的, 如果想要访问就需要在jdbc和jdbc之间加一个适配器
桥接(Bridge)
桥接模式的精髓在于分离抽象与具体实现
举个简单的例子
A向B赠送礼物,礼物Gift有大的BigGift, 中的MiddleGift , 小的SmallGift
其中大的礼物有大象,坦克,大炮, 中的礼物有电脑,手机,鼠标 小的礼物有蜂鸟,鱼… 桥梁模式可以在抽象主线(Gift) 和 实现主线(GiftImpl)上并行发展 在抽象主线的实现类的构造中传入顶级实现主线父类(GiftImpl)便是巧妙地运用了桥接模式,类图如下:
命令(Command/Action/Transaction)
命令模式一般有do和undo, do是指进行某一操作,undo 是指相应do命令命令的撤销
原型/克隆(Prototype)
BeanUtils.copyProperties
对象的克隆
备忘录(Memento)
记录快照
存盘
序列化
TemplateMethod(模板方法/钩子函数)
技术源码中最常用的设计模式之一
方法重写
Service层的方法不同实现
State(状态)
根据状态的不同进行一系列的不同操作
Intepreter(解释器)
用来做动态脚本解析
标签:getInstance,class,那些,INSTANCE,static,几年,new,设计模式,public 来源: https://blog.csdn.net/weixin_43769053/article/details/110502249