Java设计模式

前言

  • Java的设计模式在面试时也会经常问到,他总共有23种设计模式,设计模式就是解决问题的方案,也是一种编码思想。
  • Java的设计模式主要分为三类:
    • 创建型模式
      共五种:工厂(简单工厂(工厂方法模式)、抽象工厂模式)、单例模式、建造者模式、原型模式。
    • 结构型模式
      共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    • 行为型模式
      共十一种:策略模式、模板方法模式、观察者模式(监听器)、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
  • 一般情况下工厂模式的以Factory结尾;工具类通常使用单例模式,以Util结尾;建造者模式通常以Builder结尾;适配器模式通常以Adapter结尾;装饰器模式通常以Decorator结尾。
  • 常用到的设计模式其实也不多。

单例模式

  • 单例模式就是一个类只有一个实例
  • 单例模式分为饿汉模式和懒汉模式
  • 枚举是饿汉式的单例模式

饿汉式

  • 类加载时就创建实例。
  • 满足饿汉的单例模式有以下几个条件
    • 不能让调用者创建:构造方法私有化
    • 给调用者提供对象并且调用者不能创建对象:声明方法返回对象并且方法是静态方法
    • 返回的对象是同一个并且类加载时就创建:声明字段创建对象,并且使用static final 修饰,并且赋初始值(静态方法可以调用多次,返回的对象就不是同一个了,对象不能修改,所以用final修饰,静态方法中返回值得是静态的,所以用static修饰)
  • 饿汉模式是不用考虑线程并发安全问题的,因为static修饰的是在类加载的时候加载,JVM在类加载时,只会加载一次,所以实例也只会创建一次,并且实例用final修饰,一旦赋值就不能改变,所以从始至终类的实例只会有一个。
c 复制代码
public class Student {
    public static final Student student = new Student();

    private Student(){}

    public static Student getInstance(){
        return student;
    }
}
//测试
Student instance = Student.getInstance();
Student instance1 = Student.getInstance();
//测试的结果是两次获取的对象都是同一个
System.out.println(instance1.hashCode());
System.out.println(instance.hashCode());

懒汉式

  • 使用的时候才创建实例
  • 懒汉式满足的条件:
    • 不能让调用者创建:构造方法私有化
    • 给调用者提供对象并且调用者不能创建对象:声明方法返回对象并且方法是静态方法
    • 返回的对象是同一个:声明字段创建对象,使用static修饰
    • 使用实例的时候才创建:声明字段创建对象时不能用final修饰,并且初始值赋为null(final修饰后一旦赋值就不可更改,使用实例时就不能赋值了)
    • 使用实例的时候判断没有实例才创建:双重判断加同步代码块判断是否已经有实例
  • 版本一:
    • 如果多个线程同时进入判断,比如线程1来的时候employee为空,满足条件就给employee赋值,当线程1还没给employee赋值成功时,线程2来了,判读employee还是为空,线程2也给employee赋值,那么就有多个Employee的实例了。
c 复制代码
public class Employee {
    public static Employee employee = null;
    private Employee(){}
    public static Employee getInstance(){
        if(employee == null){
            employee = new Employee();
        }
        return employee;
    }
}
  • 版本二
  • employee赋值时候使用同步代码块,并且同步锁要是全局的,所以用字节码对象
  • 这样还是会有问题:
    • 并发情况下,多个线程都满足employee为空(假设线程1 和线程2 都满足),然后尝试获取锁,线程1获取到了锁,然后给employee赋值并释放锁;线程2获取到了锁,又去给employee赋值,那么又会出现多个实例
c 复制代码
public class Employee {
    public static Employee employee = null;
    private Employee(){}
    public static Employee getInstance(){
        if(employee == null){
            synchronized (Employee.class){
                employee = new Employee();
            }
        }
        return employee;
    }
}
  • 版本三
  • 在加锁后还要判断employee是否为空
c 复制代码
public class Employee {
    public static Employee employee = null;
    private Employee(){}
    public static Employee getInstance(){
        if(employee == null){
            synchronized (Employee.class){
                if(employee == null){
                    employee = new Employee();
                }
            }
        }
        return employee;
    }
}

枚举

  • 枚举的构造器默认私有化的,为枚举类定义一个实例就是单例
c 复制代码
enum Teacher {
    INSTANCE;
}
//使用
Teacher.INSTANCE

装饰者模式

  • 对原有功能进行增强
  • 装饰者模式的实现步骤
    • 增强类继承原始类
    • 增强类提供无参构造方法
    • 增强类提供原有类的属性和参数为原有类的构造方法
    • 增强类重写原有类要增强的方法
c 复制代码
//原有类
public class Target {
	//只能判断字符串是否为null
    public Boolean isEmpty(String str){
        return str == null;
    }
}
//增强类
public class TargetDecorate extends Target {
    private Target target;

    public TargetDecorate(){

    }

    public TargetDecorate(Target target){
        this.target = target;
    }
	//不仅能判断字符串是否为null,还能判断是否为""
    @Override
    public Boolean isEmpty(String str) {
        Boolean empty = target.isEmpty(str);
        if(!empty){
            return  "".equals(str);
        }
        return empty;
    }
}
//测试
@Test
public void test1(){
    TargetDecorate targetDecorate = new TargetDecorate(new Target());
    System.out.println(targetDecorate.isEmpty(""));
}

工厂模式

  • 就是用来创建对象的,实现解耦合

简单工厂

  • 一个工厂生产同类的不同品牌产品
  • 缺点:扩展困难
c 复制代码
public interface IPhone {
    void call();
}
public class Huawei implements IPhone{
    @Override
    public void call(){
        System.out.println("华为");
    }
}
public class Xiaomi implements IPhone{
    @Override
    public void call(){
        System.out.println("小米");
    }
}
//这里的类型可以用ConcurrentHashMap来存
public class PhoneFactory {
    public IPhone getPhone(String type){
        if("huawei".equals(type)){
            return new Huawei();
        }else if("xiaomi".equals(type)){
            return new Xiaomi();
        }
        return null;
    }
}
//上面的类修改后
public class PhoneFactory {
    public static ConcurrentHashMap<String,IPhone>  hashMap = new ConcurrentHashMap();
    static{
        initHashMap();//静态代码块中的方法随着类加载而执行
    }
    private static void initHashMap(){
        hashMap.put("huawei",new Huawei());
        hashMap.put("xiaomi",new Xiaomi());
    }
    public IPhone getPhone(String type){
        return hashMap.get(type);
    }
}
    @Test
    public void test2(){
       PhoneFactory phoneFactory = new PhoneFactory();
        IPhone xiaomi = phoneFactory.getPhone("xiaomi");
        IPhone huawei = phoneFactory.getPhone("huawei");
        xiaomi.call();//小米
        huawei.call();//华为
    }

工厂方法

  • 每个工厂实现抽象工厂,每个产品实现抽象产品,一个工厂创建一个品牌的产品。
c 复制代码
public interface IPhone {
    void call();
}
public interface IPhoneFactory {
    IPhone getPhone();
}
public class Huawei implements IPhone {
    @Override
    public void call(){
        System.out.println("华为");
    }
}
public class Xiaomi implements IPhone {
    @Override
    public void call(){
        System.out.println("小米");
    }
}
public class HaweiFactory implements  IPhoneFactory{
    @Override
    public Huawei getPhone(){
        return new Huawei();
    }
}
public class XiaomiFactory implements IPhoneFactory {
    @Override
    public Xiaomi getPhone() {
        return new Xiaomi();
    }
}
    @Test
    public void test3(){
        Xiaomi xiaomi = new XiaomiFactory().getPhone();
        xiaomi.call();//小米
        Huawei huawei = new HaweiFactory().getPhone();
        huawei.call();//华为
    }

抽象工厂

  • 抽象工厂中可以生产不同的产品,每个工厂创建同一品牌的不同产品,不能生产的直接返回null.
c 复制代码
public interface IPad {
    public void tv();
}
public interface IPhone {
    void call();
}
public interface IFactory {
    IPhone getPhone();
    IPad getPad();
}
public class HuaweiPhone implements IPhone {
    @Override
    public void call() {
        System.out.println("华为");
    }
}
public class HuaweiPad implements IPad {
    @Override
    public void tv() {
        System.out.println("华为的pad");
    }
}
public class XiaomiPhone implements IPhone {
    @Override
    public void call(){
        System.out.println("小米");
    }
}
public class HaweiFactory implements IFactory {
    @Override
    public HuaweiPhone getPhone(){
        return new HuaweiPhone();
    }

    @Override
    public IPad getPad() {
        return new HuaweiPad();
    }
}
public class XiaomiFactory implements IFactory{
    @Override
    public IPhone getPhone() {
        return new XiaomiPhone();
    }

    @Override
    public IPad getPad() {
        return null;
    }
}
    @Test
    public void test4(){
        IPad huaweiPad = new HaweiFactory().getPad();
        huaweiPad.tv();//华为的pad
        IPhone huaweiPhone = new HaweiFactory().getPhone();
        huaweiPhone.call();//华为
        IPhone xiaomiPhone = new XiaomiFactory().getPhone();
        xiaomiPhone.call();//小米
        IPad xiaomiPad = new XiaomiFactory().getPad();
        xiaomiPad.tv();//空指针,因为小米没有pad
    }

模版模式

  • 在一个方法中定义了一个算法的骨架,允许子类在不改变算法结构的情况下重定义某些步骤的具体内容。模板方法模式通常用于实现一个算法的不变部分,而将可变的行为留给子类来实现。
  • Spring的容器刷新就是用的模版模式
  • 其实就是一个抽象类里面定义类模版方法,这个模版方法按照一定次序调用了很多这个类的抽象方法,使用这个模版的类就要继承这个模版类,必须实现它的抽象方法,也就是模版中的内容自己定义,模版只是定义了框架和顺序。
c 复制代码
public abstract class FoodPreparer {
  
    // 模板方法  
    public void prepareFood() {
        prepareIngredients();  
        cook();  
        serve();  
    }  
  
    // 步骤1:准备食材,抽象方法由子类实现  
    public abstract void prepareIngredients();
  
    // 步骤2:烹饪,抽象方法由子类实现  
    public abstract void cook();  
  
    // 步骤3:服务/上菜,抽象方法由子类实现  
    public abstract void serve();  
}


public class PastaPreparer extends FoodPreparer {

    @Override
    public void prepareIngredients() {
        System.out.println("Preparing pasta ingredients.");
    }

    @Override
    public void cook() {
        System.out.println("Cooking pasta.");
    }

    @Override
    public void serve() {
        System.out.println("Serving pasta.");
    }
}



public class PizzaPreparer extends FoodPreparer {

    @Override
    public void prepareIngredients() {
        System.out.println("Preparing pizza ingredients.");
    }

    @Override
    public void cook() {
        System.out.println("Baking pizza.");
    }

    @Override
    public void serve() {
        System.out.println("Serving pizza.");
    }
}



    @Test
    public void test() throws Exception{
        FoodPreparer pastaPreparer = new PastaPreparer();
        pastaPreparer.prepareFood(); // 输出准备意大利面的整个过程
        System.out.println("======================");
        FoodPreparer pizzaPreparer = new PizzaPreparer();
        pizzaPreparer.prepareFood(); // 输出准备披萨的整个过程
    }

适配器模式

  • 用于解决接口不兼容的问题,使得原本没有任何关系的接口可以协同工作
  • 下面的例子是美国插头适配中国插座的例子
c 复制代码
// 目标接口:中国插座  
public interface ChineseSocket {  
    void plugIn();  
}  
  
// 适配者类:美国插头  
public class AmericanPlug {  
    public void connect() {  
        System.out.println("美国插头已连接。");  
    }  
}  

// 类适配器:美国插头适配到中国插座  
public class PowerAdaptor extends AmericanPlug implements ChineseSocket {  
    // 适配器类不需要重写任何方法,因为它直接继承了适配者的方法,并且已经实现了目标接口  
  
    // 客户端可以通过这个适配器类来调用目标接口的方法  
    public void plugIn() {  
        // 在调用适配者的方法之前或之后,可以添加一些额外的逻辑  
        System.out.println("美国插头正在适配到中国插座...");  
        // 调用适配者的方法  
        connect();  
        // 适配成功后的额外逻辑  
        System.out.println("美国插头已成功适配到中国插座。");  
    }  
}  


@Test
public void test(){
	// 创建适配器对象  
    ChineseSocket chineseSocket = new PowerAdaptor();  
   // 通过适配器对象调用目标接口的方法  
    chineseSocket.plugIn();  
}

代理模式

  • 客户端并不直接调用实际的对象,而是通过调用代理对象,来间接的调用实际的对象,相当于经纪人。
  • 代理分为静态代理和动态代理。
  • 代理可以增加在原业务的基础上增加功能。

静态代理

  • 每个类都要创建一个代理类
c 复制代码
//接口
public interface ISongHua {
    void songHua();
}
//被代理类
public class XiaoMing implements ISongHua{

    @Override
    public void songHua() {
        System.out.println("小明送花给小兰");
    }
}
//代理类
public class XiaoMingProxy implements ISongHua{
    private XiaoMing xiaoMing;

    public XiaoMingProxy() {
    }

    public XiaoMingProxy(XiaoMing xiaoMing) {
        this.xiaoMing = xiaoMing;
    }

    @Override
    public void songHua() {
        System.out.println("送花之前的逻辑");
        //送花的逻辑
        xiaoMing.songHua();
        System.out.println("送花之后的逻辑");
    }
}
	//测试
@Test
public void SongHuaTest(){
    new XiaoMingProxy(new XiaoMing()).songHua();
}
//结果
送花之前的逻辑
小明送花给小兰
送花之后的逻辑

动态代理

  • 动态代理分为jdk动态代理和cglib动态代理
  • jdk动态代理只能代理实现了接口的类(如上述的XiaoMing),cglib动态代理能代理没有实现接口的类,默认是jdk动态代理

JDK动态代理

  • jdk代理中主要有java.lang.reflect.Proxy 类和java.lang.reflect.InvocationHandler接口
  • 代理类和被代理类会实现同一个接口,只是这里是隐式的,是在JVM 在运行时动态生成的。
  • InvocationHandler中的invoke 方法用来处理被代理类方法,对被代理类的方法进行增强
  • Proxy的静态方法newProxyInstance 用来动态生成代理类,代理类实现了目标对象所实现的接口,并将方法调用委托给 InvocationHandler
  • 代理类的名字通常是$Proxy后加上一个数字

Class<?> proxyClass = Proxy.getProxyClass(类加载器,类实现的接口);

System.out.println("Proxy class name: " + proxyClass.getName());

//结果:Proxy class name: 包名.$Proxy7

  • 代理对象(实例)的名字通常是:被代理类名@哈希码

被代理类实现的接口proxy = (被代理类实现的接口) Proxy.newProxyInstance(类加载器,类实现的接口,InvocationHandler对象);

//结果:Proxy class name: 包名.被代理类@47f37ef1

  • jdk动态代理主要用于实现aop
c 复制代码
//接口
interface Hello {
    void sayHello();
}
//被代理类(必须实现接口)
public class SimpleHello implements Hello{

    @Override
    public void sayHello() {
        System.out.println("Hello, world!");  
    }  
} 
//调用处理器
public class HelloInvocationHandler implements InvocationHandler {
    //目标对象,也就是被代理的对象
    private Object target;

    public HelloInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * @param proxy  被代理的对象
     * @param method 被代理对象的方法
     * @param args   被代理对象方法中的参数
     * @return
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行前的操作");
        //调用被代理的方法,target就是被代理类的对象,args就是被代理类对象中方法的参数
        Object result = method.invoke(target, args);
        System.out.println("方法执行后的操作");
        //原始方法的执行结果
        return result;
    }
}  
//测试
@Test
public void JdkTest() {
    // 创建目标对象
    SimpleHello hello = new SimpleHello();

    // 创建InvocationHandler对象,并传入目标对象
    InvocationHandler handler = new HelloInvocationHandler(hello);
    //创建类加载器
    ClassLoader classLoader = hello.getClass().getClassLoader();
    //找到类实现的接口
    Class<?>[] interfaces = hello.getClass().getInterfaces();
    // 创建代理对象
    Hello proxy = (Hello) Proxy.newProxyInstance(classLoader, interfaces, handler);

    // 调用代理对象的方法
    proxy.sayHello();
}

CGLIB动态代理

  • org.springframework.cglib.proxy.Enhancer 类,org.springframework.cglib.proxy.MethodInterceptor接口,
  • org.springframework.cglib.proxy.MethodProxy
  • Enhancer类创建代理类
  • MethodInterceptor拦截方法,做方法增强
  • MethodProxy是Java反射中 Method 类的一个补充。它提供了一种更高效的方式来调用方法
c 复制代码
//被代理类
public class HelloTarget {
    public void sayHello(String name) {  
        System.out.println("Hello, " + name + "!");  
    }  
}
//方法拦截器
public class SimpleInterceptor implements MethodInterceptor {
    /**
     *
     * @param obj 被代理对象
     * @param method 被代理对象的方法
     * @param args 被代理对象方法的参数
     * @param proxy cglib 代理后的方法,一般不用
     * @return
     */
    @Override  
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("目标方法执行前操作");
        // 调用目标方法:cglib代理后的方法.invokeSuper(被代理对象,被代理对象方法的参数)
        Object result = proxy.invokeSuper(obj, args);  
        System.out.println("目标方法执行后操作");
        //被代理方法返回的结果
        return result;
    }  
}
//测试
@Test
public void CglibTest() throws Exception{
    // 创建目标对象
    HelloTarget target = new HelloTarget();

    // 创建 Enhancer 对象,并设置目标类
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloTarget.class);

    // 设置回调接口
    enhancer.setCallback(new SimpleInterceptor());

    // 创建代理对象
    HelloTarget proxy = (HelloTarget) enhancer.create();

    // 调用代理对象的方法
    proxy.sayHello("World");
}

策略模式

  • 它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
  • 策略模式一般情况下都是用来优化if-else语句的
  • 简单工厂中用ConcurrentHashMap改良就是策略模式
c 复制代码
//策略接口
public interface PaymentStrategy {  
    public double pay(double money);  
}
//策略类1
public class CashPayment implements PaymentStrategy {  
    @Override  
    public double pay(double money) {  
        System.out.println("Cash Payment of " + money);  
        return money;  
    }  
}  
//策略类2
public class CardPayment implements PaymentStrategy {  
    @Override  
    public double pay(double money) {  
        System.out.println("Card Payment of " + money);  
        return money - money * 0.02;  // 假设刷卡有2%的优惠  
    }  
}
//策略上下文类
public class PaymentContext {  
    private PaymentStrategy strategy;  
  
    public PaymentContext(PaymentStrategy strategy){  
        this.strategy = strategy;  
    }  
  
    public double executePayment(double money){  
        return this.strategy.pay(money);  
    }  
}
//测试
	@Test
    public void strategyTest(){  
        PaymentContext paymentContext = new PaymentContext(new CashPayment());  
        System.out.println("Cash Payment: " + paymentContext.executePayment(100));  
  
        paymentContext = new PaymentContext(new CardPayment());  
        System.out.println("Card Payment: " + paymentContext.executePayment(100));  
    }  
相关推荐
Kisorge23 分钟前
【C语言】指针数组、数组指针、函数指针、指针函数、函数指针数组、回调函数
c语言·开发语言
路在脚下@1 小时前
spring boot的配置文件属性注入到类的静态属性
java·spring boot·sql
森屿Serien1 小时前
Spring Boot常用注解
java·spring boot·后端
Damon_X1 小时前
桥接模式(Bridge Pattern)
设计模式·桥接模式
轻口味1 小时前
命名空间与模块化概述
开发语言·前端·javascript
苹果醋32 小时前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
晓纪同学2 小时前
QT-简单视觉框架代码
开发语言·qt
威桑2 小时前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
Hello.Reader2 小时前
深入解析 Apache APISIX
java·apache
飞飞-躺着更舒服2 小时前
【QT】实现电子飞行显示器(简易版)
开发语言·qt