Java设计模式

发布于:2025-05-25 ⋅ 阅读:(28) ⋅ 点赞:(0)

08. Java设计模式

1、什么是设计模式

设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。

2、设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。

1.可以提高程序员的思维能力、编程能力和设计能力。

2.使程序设计更加标准化、代码编制更加工程化,使开发效率大大提高,从而缩短软件的开发周期。

3.使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

3、java设计模式类型

根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。

创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。提供了单例、原型、工厂方法、抽象工厂、建造者5种创建型模式。

结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,提供了代理、适配器、桥接、装饰、外观、享元、组合7种结构型模式。

行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器11种行为型模式。

4、23种设计模式

  1. 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

  2. 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

  3. 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。

  4. 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

  5. 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

  6. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

  7. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

  8. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

  9. 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。

  10. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

  11. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。

  12. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

  13. 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

  14. 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

  15. 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

  16. 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

  17. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。

  18. 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。

  19. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。

  20. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

  21. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。

  22. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

  23. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

5、常用的设计模式

5.1 单例模式

在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点;
单例模式的结构
单例类:包含一个实例且能自行创建这个实例的类。
访问类:使用单例的类。

其结构如图所示。

image.png

Singleton 模式通常有两种实现形式。

第 1 种:懒汉式单例

该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例

/*
 * 懒汉式单例(在第一次访问时,才创建对象)
 */
public class Singleton {
    
    //定义静态的
    private static Singleton instance = null;  
    
    //让构造函数为 private,这样该类就不会被实例化(在其他类中)
    private Singleton (){}  
    
    //对外提供访问单个对象方法, 懒汉式单例在多线程情况下,存在问题,需要加锁
    public static synchronized Singleton getInstance() { 
            
        if (instance == null) {  
             //A 休眠了  B进来了
            instance = new Singleton();  
        }  
      return instance;  
    }
}
第 2 种:饿汉式单例

该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。

/*
 * 饿汉式单例(急切式)  不存在线程安全问题
 */
public class Singleton {
    
       //创建 Singleton 的一个对象
       private static Singleton instance = new Singleton();
     
       //让构造函数为 private
       private Singleton(){}
     
       //获取唯一可用的对象    
       public static Singleton getInstance(){
          return instance;
       }
}

JDK代理:底层使用反射机制实现,目标类必须要有实现接口。

CGLIB:底层是动态创建目标类的子类对象(动态生成子类的字节码),目标类不需要实现任何接口。

Spring 中AOP实现方式使用的就是动态代理来实现

Spring 会自动选择实现方式“

当目标类有接口,使用jdk实现。

当目标类没有接口,使用cglib实现。

5.2 工厂模式(Factory Pattern)

工厂方法(FactoryMethod)模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成.

image.png
/*
 * 画图形工厂,负责生产对象
 */
public class ShapeFactory {
    
        //使用 getShape 方法获取形状类型的对象
       public Shape getShape(String shapeType){
          if(shapeType == null){
             return null;
          }        
          if(shapeType.equalsIgnoreCase("CIRCLE")){
             return new Circle();
          } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
             return new Rectangle();
          } else if(shapeType.equalsIgnoreCase("SQUARE")){
             return new Square();
          }
          return null;
       }
       
       //反射实现
       public Shape getShape1(String className){
           if(className == null){
               return null;
           }else{
                try {
                    return (Shape) Class.forName(className).newInstance();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                    return null;
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    return null;
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    return null;
                }
           }      
       }   
}

5.3 代理模式

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

  • 代理对象可以扩展目标对象的功能;

  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

模式的结构:

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

其结构图如图所示。

image.png

代理实现可以分为静态代理和动态代理。

静态代理:

静态代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。

/*
 * 静态代理类
 */
public class StaticProxy implements Subject{
    
    // 被代理类的实例
    private Subject subject;//接收目标对象,
    
    // 将被代理者的实例传进动态代理类的构造函数中
    public StaticProxy(Subject subject) {
        this.subject = subject;
    }
    
    @Override
    public void visit() {
        System.out.println("before");
        subject.visit();//实际执行的还是目标对象的方法
        System.out.println("after");
    }

}
动态代理:

动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等。

动态代理分为jdk动态代理(基于接口的动态代理)和cglib动态代理(基于子类的动态代理)。

(1)jdk代理(基于接口的动态代理)

动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。
其步骤如下:

  1. 编写一个委托类的接口,即静态代理的(Subject接口)

  2. 实现一个真正的委托类,即静态代理的(RealSubject类)

  3. 创建一个动态代理类,实现InvocationHandler接口,并重写该invoke方法

  4. 在测试类中,生成动态代理的对象。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


/*
 * 动态代理类
 */
public class DynamicProxy implements InvocationHandler{
    
     // 被代理类的实例
     private Object object;
     
     // 将被代理者的实例传进动态代理类的构造函数中
     public DynamicProxy(Object object) {
            this.object = object;
     }
     
    /*
     * 覆盖InvocationHandler接口中的invoke()方法
     *    Object proxy 表示代理对象
     *    Method method 代理对象中的方法
     *    Object[] arg2  表示代理方法中的参数
     * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
     * 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
     * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
     * 代码切入的扩展点了。
     */
     
     //方法的调用时间:当动态代理对象调用实际对象方法时,被invoke方法截获
    @Override    //最大特点:动态的将目标对象,以及目标对象中的方法出传递到过来,
    public Object invoke(Object proxy, Method method, Object[] arg2) throws Throwable {
        
        System.out.println("before");
                        //这才是实际调用真实主题(目标对象)方法的地方
        Object result = method.invoke(object, arg2);
        
        System.out.println("after");
            return result;
    }
}

测试类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {

     public static void main(String[] args) {
         //我们要代理的真实对象
         Subject realSubject = new RealSubject();
         
         //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
         InvocationHandler  dynamicProxy = new DynamicProxy(realSubject);
         
        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数dynamicProxy, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */          
                               //在运行时,去真正的创建动态代理对象,那我们就要告诉动态代理对象,实际应该代理的目标对象
         Subject subject = (Subject) Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), dynamicProxy);
         
         subject.visit(); 
         //subject.visit1(); 
    }
}
(2)cglib代理(基于子类的动态代理)

pom导入jar包

    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.1_3</version>
    </dependency>
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * Cglib子类代理工厂
 * 对RealSubject在内存中动态构建一个子类对象
 */
public class CGLibProxy implements MethodInterceptor { 

        //维护目标对象
        private Object target;

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

        //给目标对象创建一个代理对象
        public Object getProxyInstance(){
            //1.工具类,为非接口类型创建一个JAVA代理,Enhancer动态的创建给定类的子类并且拦截代理类的所有的方法
            Enhancer en = new Enhancer();
            //2.设置父类
            en.setSuperclass(target.getClass());
            //3.设置回调函数
            en.setCallback(this);
            //4.创建子类(代理对象)
            return en.create();

        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("开始事务...");

            //执行目标对象的方法
            Object returnValue = method.invoke(target, args);

            System.out.println("提交事务...");

            return returnValue;
        }
}

测试:

public class Test {
     public static void main(String[] args) {
         //目标对象
         RealSubject target = new RealSubject();
         //代理对象
         RealSubject proxy = (RealSubject)new CGLibProxy(target).getProxyInstance();
         //执行代理对象的方法
          proxy.visit();
    }
}


喜欢的朋友记得点赞、收藏、关注哦!!!


网站公告

今日签到

点亮在社区的每一天
去签到