常见设计模式总结(简易版)

  • 设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

设计模式七大原则

  • 单一职责原则:一个类应该只负责一项职责。提高可读性维护性、降低耦合。
  • 接口隔离原则:类之间的依赖关系应该建立在最小的接口上。例如实现类只能用到接口的部分方法,为了降低冗余提高可维护性,对原接口进行合理的拆分和组合。
  • 依赖倒转原则:高层模块不应该直接依赖于低层模块,两者都应该依赖于抽象。即详细应该依赖于抽象。在调用链上,调用者属于高层,被调用者属于低层。例如controller层不能直接依赖于service层,他们都应该依赖于抽象,即service接口。
  • 里氏代换原则:子类引用指向父类对象后功能未变,子类中尽量不要重写父类的方法。
  • 开闭原则:软件对扩展开放,对修改关闭。提高扩展性和可维护性。
  • 迪米特法则:最少知道原则,一个类对自己依赖的类知道的越少越好,只与直接的朋友(成员变量,方法参数,方法返回值的类)通信。
  • 合成复用原则:尽量使用聚合或组合的方式,而不是使用继承。也就是把需要用到的类作为本类的参数、成员变量、局部变量。

设计模式类型 

  • 创建型模式:用于对象的创建,如:工厂模式、单例模式。
  • 结构性模式:描述对象之间的组合关系,如:代理模式、外观模式。
  • 行为型模式:描述对象之间的通信以及责任分配,如:策略模板、模板方法模式、观察者模式。 

工厂模式

        工厂模式解决了对象的创建过程的灵活性和可维护性问题,允许在不暴露对象创建逻辑的情况下,统一由工厂类创建对象并返回,从而降低了代码的耦合性。

优点:避免直接使用new关键字创建对象带来的耦合性,方便后期维护;将对象的创建逻辑封装到一个工厂类中,提高了代码的复用性;降低耦合性、提高复用性。

分类:

  1. 简单工厂:简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。【提前把所有同类对象放入到工厂这个类里面,工厂只提供同类对象的参数接口,调用者只需传入具体的参数,在工厂内部选择具体的实现
  2. 工厂方法:工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节
  3. 抽象工厂:抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。

简单工厂、工厂方法、抽象工厂的区别:

  • 简单工厂:用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。
  • 工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。
  • 抽象工厂:用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族。

单例模式

  • 单列模式:保证一个类只有一个实例,并且只提供一个取得对象实例的静态方法。
  • 使用场景:需要频繁的进行创建和销毁对象、创建对象时耗时过多或耗费资源过多但又经常用到的对象、工具类对象......
  • 优点:
    • 节省资源:只有一个实例,避免重复创建对象,从而节省了资源,提高了系统性能。
    • 管理全局变量:单例模式可以用于管理全局状态和变量,方便在整个系统中共享数据
  • 分类:①饿汉式;②简单懒汉式(在方法声明时加锁);③DCL双重检验加锁(进阶懒汉式);④静态内部类(优雅懒汉式);⑤枚举。
    •  饿汉式:还没用到就直接初始化了对象。
    • 懒汉式:等到用到的时候才进行初始化。
    • DCL双重检验加锁中使用volatile关键字修饰:
      • 保证变量的可见性,修改后立即更新到内存中。
      • 防止指令重排,用于保证对象被正确创建后才被其他线程正确使用。
  • 饿汉式和懒汉式的对比:
    • 执行效率:饿汉式没有加任何锁,执行效率相对较高;懒汉式内部一般加synchronized修饰,效率较低。
    • 性能上:饿汉式在类加载的时候就初始化,不管是否使用都会实例化,占据内存空间,浪费内存;懒汉式在需要的时候才进行实例化,相对来说不浪费内存。

DCL中使用双重检查的方式可以减少锁的竞争,提高性能。

// 饿汉式单例模式
public class Singleton{
    // 构造器私有化
    private Singleton(){}
    // 类的内部创建对象
    private final static Singleton singleton = new Singleton();
    // 向外暴露一个静态的公共方法
    public static Singleton getInstance(){
        return singleton;
    }
}

// 简单懒汉式(加同步锁)
public class Singleton{
    // 构造器私有化
    private Singleton(){}
    private static Singleton singleton = null;
    public static synchronized Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}
// DCL懒汉式
public class Singleton{
    // 构造器私有化
    private Singleton(){}
    // 此处加volatile①禁止指令重排,②保证对共享变量的修改的可见性
    private static volatile Singleton singleton = null;
    public static Singleton getInstance(){
        if(singleton == null){
        // 第一次检查可能会有多个线程同时检查
        // 类级别的锁对象,锁对象是全局的,对该类的所有实例都有效。
            synchronized(Singleton.class){
                if(singleton == null){	// 第二次检查,只有一个线程进入检查并创建实例
                    singleton = new Singleton();
                }

                // 使用双重检查的方式可以减少锁的竞争,提高性能。
            }    
        }
        return singleton;
    }
}

// 内部类
public class Singleton{
    // 构造器私有化
    private Singleton(){}
    private static class SingletonInstance{
        private static final Singleton singleton = new Singleton();
    }
    private static Singleton getInstance(){
        return SingletonInstance.singleton;
    }
}

// 枚举类
public enum Singleton{
    INSTANCE;
}

代理模式

代理模式概述:

  • 代理模式属于结构型的模式。指一个对象本身不做实际的操作,而是通过其他对象来得到自己想要的结果。

        静态代理

        目标对象和代理对象实现相同的接口,在程序运行之前,代理类字节码.class就已经编译好了。

        JDK动态代理

        动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。

        编写一个类实现InvocationHandler接口,然后重写invoke方法,这个invoke方法就是我们提供的代理方法。通过Proxy类的newProxyInstance()方法,传入invocationHandler接口的实现类等参数,返回一个代理对象。并且生成的代理类实现了原来那个类的所有接口,并对接口的方法【就是Invoke方法】进行了代理,我们通过代理对象调用这些方法的时候,底层会通过反射,调用我们实现的invoke方法。

其中invoke()方法中的method.invoke(target, args)就是利用Java反射机制在运行时动态地在内存中生成代理对象。

CGLIB动态代理

        CGLIB动态代理可以直接代理类,JDK动态代理目标业务类必须实现接口。

简单原理:通过自定义实现拦截器接口(MethodInterceptor)的类,并重写intercept()用于拦截增强被代理类的方法【类似于JDK动态代理中的invoke()方法】。通过Enhancer 类的 create()创建简单的代理类。

        CGLIB采用非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术的拦截所有父类的方法调用,顺势织入横切逻辑。(CGLIB在字节码的基础上,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

JDK动态代理和CGLIB动态代理的对比:

  • JDK动态代理只能代理实现了接口的类,而CGLIB可以代理未实现任何接口的类。
  • JDK动态代理是实现了被代理对象所实现的接口;CGLIB是继承了被代理对象。
  • JDK和CGLIB都是在运行期生成字节码,JDK是直接写Class字节码,CGLIB是使用ASM框架写Class字节码。

外观模式

        外观模式->结构型模式

        外观模式:也称为门面模式,隐藏系统的复杂性,并向客户端提供一个客户端可以访问系统的接口。它向现有的系统添加一个接口,用这一个接口来隐藏实际的系统的复杂性。使用外观模式,他外部看起来就是一个接口,其实他的内部有很多复杂的接口已经被实现。

        例子:用户注册完之后,需要调用阿里短信接口,邮件接口,微信推送接口。

策略模式

        策略模式是一种行为型模式。当在处理一个业务时,有多种处理方式,并且需要在运行时决定使用哪一种具体的实现时,就会使用策略模式。

使用场景:

  • 一个系统需要动态地在几种算法中选择一种:我要一个支付模块,我要有微信支付、支付宝支付、银联支付等
  • 如果一个对象有很多行为,如果不使用恰当的模式,这些行为就只好只用多重的条件选择语句来实现。

使用方式:

  1. 定义策略接口。
  2. 建立策略接口的具体实现类。
  3. 重新定义需要的服务,在服务里面传入具体的策略接口的实现类。

模板方法模式

行为型模式:用以描述对象之间的通信和责任分配。

模板方法模式:定义一个操作中的算法骨架(父类),将一些方法的实现延迟到子类中【重写】。实现子类在不改变该算法结构的前提下实现重定义该算法。

使用时机:实现一些操作,整体步骤很确定,其中只有一小部分需要改变,此时可以使用模板模式。将容易改变的部分抽象出来,供子类实现。

应用场景:

    • AQS基于模板方法进行设计的,锁的实现需要继承AQS并重写它指定的方法。【AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。 】
    • 数据库访问的封装。
    • Junit单元测试。

优势:

可重用性和可维护性:由于算法的骨架在父类或者抽象类中实现,因此可以避免在每个子类中重复编写相同的代码,提高了代码的可重用性和可维护性;

可扩展性:由于子类只需要实现特定的部分而不需要修改算法的整体结构,因此可以提高代码的安全性和可扩展性;

一致性和稳定性:可以在父类或者抽象类中控制算法的结构和执行流程,从而保证算法的一致性和稳定性。

// 定义一个模板
package com.lijie;

//模板方法
public abstract class RestaurantTemplate {

	// 1.看菜单
	public void menu() {
		System.out.println("看菜单");
	}

	// 2.点菜业务
	abstract void spotMenu();

	// 3.吃饭业务
	public void havingDinner(){ System.out.println("吃饭"); }

	// 3.付款业务
	abstract void payment();

	// 3.走人
	public void GoR() { System.out.println("走人"); }

	//模板通用结构
	public void process(){
menu();
		spotMenu();
		havingDinner();
		payment();
		GoR();
	}
}

// 具体模板方法子类
package com.lijie;

public class RestaurantGinsengImpl extends RestaurantTemplate {

    void spotMenu() {
        System.out.println("人参");
    }

    void payment() {
        System.out.println("5快");
    }
}

观察者模式

        观察者模式属于行为型模式:描述对象之间的通信以及责任分配

        观察者模式:又称为发布-订阅模式,定义对象之间的一种一对多的依赖关系,使得当一个对象的状态改变的时候,所有依赖它的对象都能够得到通知并实现自动更新。观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应。

        应用场景:

    • 关联行为场景,关联行为是可拆分的,不是组合关系。
    • 事件多级触发场景。
    • 跨系统的消息交换场景:消息队列。
// 定义抽象观察者,每一个实现该接口的实现类都是具体观察者。
package com.lijie;

//观察者的接口,用来存放观察者共有方法
public interface Observer {
    // 观察者方法
    void update(int state);
}

// 定义具体观察者
package com.lijie;

// 具体观察者
public class ObserverImpl implements Observer {

    // 具体观察者的属性
    private int myState;

    public void update(int state) {
        myState=state;
        System.out.println("收到消息,myState值改为:"+state);
    }

    public int getMyState() {
 return myState;
    }
}

// 定义主题。主题定义观察者数组,并实现增、删及通知操作。
package com.lijie;

import java.util.Vector;

//定义主题,以及定义观察者数组,并实现增、删及通知操作。
public class Subjecct {
	//观察者的存储集合,不推荐ArrayList,线程不安全,
	private Vector<Observer> list = new Vector<>();

	// 注册观察者方法
	public void registerObserver(Observer obs) {
		list.add(obs);
	}
    // 删除观察者方法
	public void removeObserver(Observer obs) {
		list.remove(obs);
	}

	// 通知所有的观察者更新
	public void notifyAllObserver(int state) {
		for (Observer observer : list) {
observer.update(state);
		}
	}
}

// 定义具体的,他继承继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多。
package com.lijie;

//具体主题
public class RealObserver extends Subjecct {
    //被观察对象的属性
	 private int state;
	 public int getState(){
		 return state;
	 }
	 public void  setState(int state){
		 this.state=state;
		 //主题对象(目标对象)值发生改变
		 this.notifyAllObserver(state);
	 }
}

// 运行测试
package com.lijie;

public class Client {
public static void main(String[] args) {
		// 目标对象
		RealObserver subject = new RealObserver();
		// 创建多个观察者
		ObserverImpl obs1 = new ObserverImpl();
		ObserverImpl obs2 = new ObserverImpl();
		ObserverImpl obs3 = new ObserverImpl();
		// 注册到观察队列中
		subject.registerObserver(obs1);
		subject.registerObserver(obs2);
		subject.registerObserver(obs3);
		// 改变State状态
		subject.setState(300);
		System.out.println("obs1观察者的MyState状态值为:"+obs1.getMyState());
		System.out.println("obs2观察者的MyState状态值为:"+obs2.getMyState());
		System.out.println("obs3观察者的MyState状态值为:"+obs3.getMyState());
		// 改变State状态
		subject.setState(400);
		System.out.println("obs1观察者的MyState状态值为:"+obs1.getMyState());
		System.out.println("obs2观察者的MyState状态值为:"+obs2.getMyState());
		System.out.println("obs3观察者的MyState状态值为:"+obs3.getMyState());
	}
}

Spring中使用到的设计模式

Spring的AOP是如何实现的

        Spring的AOP(面向切面编程)是通过动态代理实现的;在Spring中,AOP通过在运行时动态地将切面植入到目标对象的方法中,从而实现横切连接点的模块化。

        JDK动态代理:基于接口的代理,通过java.lang.reflect.Proxy类和InvocationHandler接口实现。Spring使用JDK动态代理来代理实现了接口的目标对象。

        CGLIB动态代理:基于继承的代理,通过CGLIB库生成目标对象的子类来实现代理。Spring使用CGLIB动态代理来代理没有实现接口的目标对象。
        在Spring中,通过配置文件或注解来定义通知和切点从而构成切面,然后使用AOP代理将切面织入目标对象的方法中。当目标对象的方法被调用时,AOP代理会在方法执行前、执行后或抛出异常时执行切面的相关逻辑,实现横切关注点的功能,如日志记录、事务管理等。

 JDK中常见的设计模式

 使用策略模式的例子

Collections.sort(List<T> list, Comparator<? super T> c)方法,这个方法接受一个比较器Compartor参数,客户端在运行时可以传入一个比较器的实现,sort()方法中根据不同实现,按照不同的方式进行排序。

 参考链接

设计模式面试题(总结最全面的面试题!!!)-CSDN博客

【Java面试题汇总】设计模式篇(2023版)_面试常用的设计模式java-CSDN博客

Java 代理模式详解 | JavaGuide 

  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值