java设计模式

 

设计模式概述

        设计模式是人们长期的软件开发中对一些经验的总结,是对某些特定问题经验过实践的特定解决方法

        目前所说的设计模式通常指的是GoF模式(Gang of Four,四人组),这本书的作者是:Gamma、Helm、Johnson、Vlissides。书中总结了23总经典的设计模式,因此被称为GoF设计模式。

这23总设计模式有两种分类方式

(1)根据目的的划分,即根据设计模式是用于完成何种工作来划分。这种方式可以分为创建型模式、结构型模式和行为型模式3种。

创建型模式:用于描述“如何创建对象”,其主要特点是“将对象的创建与使用分离”。

构建型模式:用于描述如何将类或对象按某种布局组成更大的结构。

行为型模式:用于描述类或对象之间如何相互协作,共同完成单个对象无法独立完成任务,可分为类模式和对象模式两种。

 (2)根据作用范围划分,即根据设计模式主要作用于类上还是主要作用于对象来划分,这种方式可以分为类模式和对象模式两种。

类模式:用于处理对象于子类之间的关系,这些关系通过继承来建立,是静态的,在编译1时刻便确定下来了。

对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时是可以变化的,更具动态性。

范围\目的创建型模式构架型模式行为型模式
类模式工厂方法(类)适配器模板方法解析器
对象模式   单例、原型、抽象工厂、建造者代理、(对象)适配器、桥接、装饰、外观、享元、组合策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录

软件可复用问题和面向对象设计原则

        在软件开发和使用过程中、需求是经常需要变化的,面对这些变化,设计不足的软件往往难以修改甚至要重新设置

        对于如何设计易于维护和扩展的软件系统,面向对象的软件设计提出了几大原则。这些原则可以用来检验软件系统设计的合理性,也被设计模式所遵守

  1. 单一职责原则(Single Responsibility Principle,SRP):一个类应该只负责一项职责,尽量保持类的单一性,减少类的耦合性

  2. 开放封闭原则(Open Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭,通过扩展来实现新的功能,而不是修改已有的代码。

  3. 里氏替换原则(Liskov Substitution Principle,LSP):派生类应该能够替换其基类并出现在基类可以出现的地方,确保子类能够完全替代父类

  4. 依赖倒置原则(Dependency Inversion Principle,DIP):要面向接口编程,模块之间的依赖应该建立在抽象接口上,而不是具体实现上

  5. 接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖它不需要的接口,一个类应该只依赖于它需要的接口,避免对无用接口的依赖

  6. 迪米特法则(Law of Demeter,LoD):一个对象应该尽可能少地与其他对象之间发生相互作用,减少对象之间的耦合度,尽可能只与直接的朋友发生交流

  7. 合成复用原则(Composite Reuse Principle,CRP):尽量使用合成/聚合而不是继承,通过组合对象,可以灵活地调整实现方式,更好地进行复用。

设计模式的应用

结合以上设计原则,针对不同的业务场景下的软件复用问题,前行者总结了多种设计模式,为了不同的业务场景下的软件可复用设计提供了参考方案。

工厂方法模式
 

厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但将具体的对象创建过程延迟到子类中实现。

在工厂方法模式中,我们将对象的创建封装到一个工厂类中,通过调用工厂类的方法来创建对象,而不是直接在客户端代码中使用new关键字来创建对象。这样做的好处是可以将对象的创建和使用分离,降低了客户端代码与具体对象之间的耦合。

工厂方法模式通常包含如下角色:

  1. 抽象产品(Product):定义了产品的接口,是具体产品对象的共同父类或接口。

  2. 具体产品(Concrete Product):实现了抽象产品定义的接口,是被创建的对象。

  3. 抽象工厂(Factory):定义了创建产品对象的接口,可以包含一个或多个工厂方法,用于创建具体产品对象。

  4. 具体工厂(Concrete Factory):实现了抽象工厂定义的接口,负责具体产品对象的创建。

使用工厂方法模式的好处是,客户端代码不需要直接依赖具体产品类,只需要通过抽象产品接口或抽象工厂接口来访问具体产品。这样,当需要替换具体产品时,只需要更改具体工厂类即可,而不需要修改客户端代码,符合开放封闭原则。

下面是一个示例代码,演示了工厂方法模式的基本结构:

// 抽象产品
interface Product {
    void operation();
}

// 具体产品
class ConcreteProduct implements Product {
    public void operation() {
        // 具体产品的操作逻辑
    }
}

// 抽象工厂
interface Factory {
    Product createProduct();
}

// 具体工厂
class ConcreteFactory implements Factory {
    public Product createProduct() {
        return new ConcreteProduct();
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        Factory factory = new ConcreteFactory();
        Product product = factory.createProduct();
        product.operation();
    }
}

在上述示例中,客户端代码通过工厂对象的createProduct()方法创建具体产品对象,并调用产品对象的operation()方法进行操作。客户端只需要关注抽象产品和工厂接口,而不需要了解具体的产品和工厂实现,实现了解耦。同时,如果需要新增其他具体产品,只需添加对应的具体产品类和具体工厂类即可,不会影响现有的代码。

工厂方法模式可以根据需要进行灵活的扩展,适用于对象创建的场景,特别是当需要动态决定创建哪种类型的对象时较为常用。通过工厂方法模式,可以提供一种统一的创建对象的方式,更好地组织和管理对象的创建过程。

工厂方法模式通过定义一个抽象接口,将产品对象的实际创建工作推迟到具体工厂实现类中。工厂方法模式的主要优点如下。

客户只需要知道具体工厂的名称就可以得到所需要的产品,无需知道产品的具体创建过程

基于多态,便于对复杂逻辑进行封装管理,并且在系统增加新的产品时只需要添加产品具体类和对应的具体工厂类,无需对原工厂进行任何修改,满足开闭原则。

其缺点就是每增加一个产品就要增加一个具体的产品类和一个对应的具体工厂类,这增加了系统的复杂

代理模式

当使用代理模式时,我们可以根据代理的实现方式将其分为静态代理和动态代理两种形式。

  1. 静态代理:

    • 静态代理是在编译时就已经确定了代理关系的一种形式。代理类在编译时就已经创建,并且代理类和具体对象之间的关系是固定的。
    • 在静态代理中,代理类和具体类都实现同一个接口或继承同一个父类,这样代理类就可以通过接口或父类引用持有具体类的实例。
    • 静态代理中的代理类负责对具体类的方法进行包装和增强,代理类可以在调用具体类方法之前或之后进行一些额外的操作。
  2. 动态代理:

    • 动态代理是在运行时根据需要动态生成代理类的一种形式。代理类的创建是在程序运行时根据被代理对象来动态生成的。
    • Java中提供了两种方式来实现动态代理:基于接口的动态代理(使用Java的Proxy类和InvocationHandler接口)和基于类的动态代理(使用CGLib库)。
    • 动态代理机制允许我们在不修改原有代码的情况下,对目标对象进行增强。通过动态代理,我们可以在调用目标对象的方法前后执行自定义的操作,例如日志记录、性能统计等。

静态代理和动态代理在实现上有一些区别,但它们的目的都是通过代理对象来间接地访问和控制目标对象

代理模式的优点包括:

  • 提供了对目标对象的访问控制和增强能力,可以在不修改原有代码的情况下实现额外的功能。
  • 将客户端代码与具体对象解耦,客户端只需要面向抽象接口编程,无需关心具体对象的创建和管理。
  • 增加代码的复用性和可维护性,可以在代理对象中添加公共的处理逻辑,避免重复代码。

然而,代理模式也存在一些缺点:

  • 静态代理需要为每一个具体对象创建一个对应的代理类,导致类的数量增加,增加了代码的维护难度。
  • 动态代理的实现相对复杂,需要了解Java反射机制和相关的API,同时在运行时动态生成代理类也会带来一定的性能开销。

代理模式在实际开发中经常被使用,例如在Spring框架中的AOP(面向切面编程)中使用了动态代理来实现对切面的管理和调用。另外,代理模式也在一些领域中被广泛应用,例如远程服务调用、缓存代理、日志记录等。

静态代理Dom

// 定义共同的接口
interface Subject {
    void doSomething();
}

// 实际对象
class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("RealSubject: Doing something...");
    }
}

// 代理对象
class ProxySubject implements Subject {
    private RealSubject realSubject;
    
    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    
    public void doSomething() {
        System.out.println("ProxySubject: Before doing something...");
        
        // 调用实际对象的方法
        realSubject.doSomething();
        
        System.out.println("ProxySubject: After doing something...");
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        
        proxySubject.doSomething();
    }
}

在上述Dom中,Subject是代理对象和实际对象的共同接口,RealSubject是实际对象的具体实现。ProxySubject是代理对象,在调用doSomething()方法时,代理对象会在实际对象的方法执行前后进行一些额外的操作。

在客户端代码中,我们首先创建了实际对象realSubject,然后将其传递给ProxySubject构造函数,创建代理对象proxySubject。最后,通过代理对象来调用doSomething()方法。在代理对象的doSomething()方法中,会先执行一些前置操作,然后调用实际对象的doSomething()方法,最后执行一些后置操作。

通过静态代理,我们可以在不修改实际对象的情况下,对其进行一些额外的处理。在上述示例中,代理对象在实际对象的方法执行前后分别添加了一些额外的打印操作

静态代理是在编译时就确定了代理关系,通过创建代理类并持有实际对象的引用来实现对实际对象的控制和扩展。使用静态代理,我们可以在不修改实际对象的代码的情况下,增加一些额外的功能或进行一些预处理和后处理的操作。

JDK动态代理

DOM

首先,我们需要定义一个共同的接口(Subject):

public interface Subject {
    void doSomething();
}

然后,创建一个实际对象(RealSubject),实现该接口:

public class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("RealSubject: Doing something...");
    }
}

接下来,创建一个实现InvocationHandler接口的代理处理器类(ProxyHandler):

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

public class ProxyHandler implements InvocationHandler {
    private Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("ProxySubject: Before doing something...");

        Object result = method.invoke(target, args);

        System.out.println("ProxySubject: After doing something...");

        return result;
    }
}

在代理处理器类的invoke方法中,我们可以进行一些额外的操作,然后调用实际对象的对应方法。

最后,在客户端代码中进行实际对象的代理创建:

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxyHandler proxyHandler = new ProxyHandler(realSubject);

        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                proxyHandler
        );

        proxySubject.doSomething();
    }
}

在客户端代码中,我们首先创建了实际对象(RealSubject),然后创建一个代理处理器(ProxyHandler)并将实际对象传递给它。接下来,使用Proxy.newProxyInstance方法创建一个代理对象。该方法接收三个参数:类加载器、实际对象所实现的接口列表、代理处理器。最后,通过代理对象来调用方法。

当调用代理对象的doSomething方法时,代理处理器的invoke方法会被触发,我们可以在该方法中进行一些额外的操作,然后调用实际对象的doSomething方法。

通过JDK动态代理实现,我们可以在运行时动态地创建代理对象,无需手动编写代理类。这样的动态代理机制使得我们能够更加灵活地对目标对象进行增强和控制。

CGLIB动态代理 DOM

使用maven导入依赖

 <dependencies>
    <!--dependency>
      <groupId>com.bdqn</groupId>
      <artifactId>[the artifact id of the block to be mounted]</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency-->

<!--    导入spring-core依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>6.0.6</version>
    </dependency>



    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>


    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.3.10</version>
    </dependency>


    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connec tor-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.33</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>

  </dependencies>

首先,我们需要引入CGLIB库,并创建一个实际对象类(RealSubject),它不需要实现任何接口:

public class RealSubject {
    public void doSomething() {
        System.out.println("RealSubject: Doing something...");
    }
}

接下来,创建一个实现MethodInterceptor接口的代理类(ProxyInterceptor):

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("ProxySubject: Before doing something...");

        Object result = proxy.invokeSuper(obj, args);

        System.out.println("ProxySubject: After doing something...");

        return result;
    }
}

在代理类的intercept方法中,我们可以进行一些额外的操作,然后调用实际对象的对应方法。

最后,在客户端代码中进行实际对象的代理创建:

import net.sf.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxyInterceptor proxyInterceptor = new ProxyInterceptor();

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(proxyInterceptor);

        RealSubject proxySubject = (RealSubject) enhancer.create();

        proxySubject.doSomething();
    }
}

在客户端代码中,我们首先创建了实际对象(RealSubject),然后创建一个代理拦截器(ProxyInterceptor)。接下来,使用Enhancer类来创建代理对象。我们需要设置两个关键的属性:setSuperclass用于指定实际对象的类,setCallback用于指定代理拦截器。最后,通过代理对象来调用方法。

当调用代理对象的doSomething方法时,代理拦截器的intercept方法会被触发,我们可以在该方法中进行一些额外的操作,然后通过MethodProxy调用实际对象的方法。

通过CGLIB动态代理实现,我们可以在运行时动态地创建代理对象,无需实现接口。这使得我们能够代理那些没有实现接口的对象。但是需要注意的是,使用CGLIB动态代理的类不能是final,否则会抛出异常。

请确保在项目中引入了CGLIB的相关依赖,并且在编译和运行时环境中可用。

小结:

设计模式是面向对象程序设计在实际应用中的集中体现。设计模式可以提高软件的复用性、使开发出来的软件更易于扩展,更容易适应需求的变化。掌握常用的设计模式可以显著提升代码质量,了解和学习设计模式是每一个开发者的必修课。

当然,一切软件的设计都基于它的需求,不能生搬硬套。如果滥用设计模式,也会导致软件设计过于复杂、软件规模过大。所以理解设计模式更需要深入体会它的使用场景 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值