其他分享
首页 > 其他分享> > 工作几年设计模式那些事~

工作几年设计模式那些事~

作者:互联网

工作几年设计模式那些事~

背景

工作几年, 随着经验的积累, 慢慢地开始往自己的代码中添加合适的设计模式, 所写代码也不再是为了单纯完成某个需求, 而是在原先基础上用更少的代码完成更多的事 , 让自己的代码更加地优雅 . 更加健壮的运行 , 下面是一些工作中用到的设计模式及读书或者博文的一些总结.

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