常用的设计模式

代理模式

在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。

代理模式常用在业务系统中开发一些非功能性需求,如:监控、统计、鉴权、限流、事务、幂等、日志等。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此以外,代理模式还可以用在RPC[远程过程调用(Remote Procedure Call)]、缓存等应用场景中。

1. 静态代理

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。静态代理的代理类由程序员创建或工具在类加载前生成。

Client.java: 使用代理

 public class Client {
     public static void mian(String[] args){
         IUserService userService = new ProxyUserServiceImpl(new UserServiceImpl());
         userService.create(101);
     }
 }

IUserService.java: 代理接口,由目标类和代理实现

 public interface IUserService {
     public void create(int id);
 }

ProxyUserServiceImpl.java: 代码类,拦截目标方法的调用

 public class ProxyUserServiceImpl implements IUserService{
     private static final Logger logger = LogManager.getLogger();
     
     private UserServiceImpl userService = null;
     
     public ProxyUserServiceImpl(UserServiceImpl userService) {
         this.userService = userService;
     }
     
     public void create(int id) {
         logger.info("create 方法被调用");
         userService.create(id);
     }
 }

UserServiceImpl.java: 目标类、被代理类

 public class UserServiceImpl implements IUserService {
     private static final Logget logger = LogManager.getLogger();
     
     public void create(int id) {
         logger.info("注册用户....");
     }
 }
2. 动态代理

动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

2.1 JDK动态代理

JDK自从1.3版本开始,就引入了动态代理,可以动态创建代理类和代理实例。代理实例是代理类的一个实例。每个代理实例都有一个关联的调用处理程序对象,它可以实现接口InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的Invoke方法,并传递代理实例,识别调用方法的java.lang.reflect.Method对象以及包含参数的Object类型的数组。调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。

Client.java:

 public class Client {
     public static void main(String[] args) {
         IUserService userService = (IUserService)Factory.create(IuserService.class,new UserServiceImpl());
         userService.create(101);
     }
 }

Factory.java: 根据传入的接口Class实例和对应的实现类实例生成代理对象

 public class Factory {
     public static Object create(Class superclass, Object target) {
         Handler handler = new Handler(target);
         return Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[] {Superclass},handler);
     }
 }

参数:

  • ClassLoader loader : 定义代理类的类加载器

  • Class<?>[] interfaces : 代理类要实现的接口列表

  • InvocationHandler h:指派方法调用的调用处理程序

Handler.java : 调用处理器

 public class Handler implements InvocationHandler {
     private static final Logger logger = LogManager.getLogger();
     private Object targerObject = null;
     
     public Handler(Object targerObject) {
         this.targetObject = targetObject;
     }
     
     @override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("Handler-->在目标方法被调用之前附加功能");
         Object object = null;
         try{
             object = method.invoke(targetObject, args);
             System.out.println("Handler-->目标方法成功执行之后附加功能");
         }catch(Throwable throwable) {
             System.out.println("Handler-->目标方法抛出异常后附加功能");
             throw throwable;
         }finally{
             System.out.println("Handler-->目标方法完成之后附加功能");
         }
         return object;
     }
 }

参数:

  • Object proxy : 当前正在调用的代理实例

  • Method method : 代理实例上调用的接口方法的Method实例

  • Object[] args : 方法调用时传入的参数,如果接口方法不使用参数,则为null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。

返回值:

  • 如果接口方法声明的返回类型是基本类型,则此方法返回的值是对应包装类型的实例;否则,它一定是可赋值给声明的返回类型的类型。如果此方法返回的值为null并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException。如果方法声明返回void,则该方法返回null。

注意:

  • 手动调用被代理的方法

  • 代理对象,不要在invoke方法中调用代理的toString方法,否则会被InvocationHandler拦截,再次调用InvocationHandler的invoke方法,从而抛出栈溢出异常

MVC

MVC是模型(model) - 视图(view) - 控制器(controller)的缩写,这种设计模式将代码分为三种角色,通过控制器完成业务逻辑与视图的解耦。

如:JavaEE Web中,如果不分离的话,可以在Servlet中完成当前模块的所有业务逻辑,然后生成响应,也就是Servlet中混合了业务逻辑和视图相关的代码,不利于视图和业务逻辑的重用。如果分离,则业务逻辑可以放到对应类中,试图可以由JSP完成,而Servlet负载业务逻辑和视图的总体控制。

虽然MVC并不是Java当中独有的,但是几乎所有的B/S的架构都采用了MVC设计模式。Java Web中这三部分分别如下:

  • 控制器-Controller:接受请求,调用模型,给出视图转发规则,如:Servlet

  • 视图-View:呈现模型中的业务数据,提供用户交互界面,比如HTML(静态资源),JSP(动态资源)等等。

  • 模型-Model:完成业务逻辑,大多时候会通过bo返回数据。如:Service、Dao、BO都属于Model。

适配器模式

适配器模式用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

  • 基于继承

     public class Client{
         public static void main(String[] args){
             ITarget target = new Adaptor();
             target.f1();
         }
     }
     ​
     public interface Itarger{
         void f1();
         void f2();
     }
     public class Adaptor extends Adaptee implements ITarget{
         public void f1(){
             super.fa();
         }
         public void f2(){
             
         }
     }

  • 基于组合

     public class Adaptor implements ITarget{
         private Adaptee adaptee;
         public Adaptor(Adaptee adaptee){
             this.adaptee = adaptee;
         }
         public void f1(){
             adaptee.fa()
         }
     }

    两种实现方式,在实际开发中,到底该选择使用哪一种?一个是Adaptee接口的个数,另一个是Adaptee和ITarget的契合程度:

    • 如果Adaptee接口并不多,两种都可

    • 如果接口多,而且Adaptee和Target接口定义大部分都相同,推荐类。代码量少

    • 如果接口多,而且Adaptee和Target接口定义大部分不相同,推荐组合,组合更灵活

使用场景

  • 统一多个类的接口设计

  • 替换依赖的外部系统

  • 兼容老版本接口,在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为deprecated,并将内部实现逻辑委托为新的接口实现。这样做的好处是,让使用它的项目有个过渡期,而不是强制进行代码修改。

应用解析

  • 适配器模式在Java日志中的应用

    slf4j提供了统一的接口定义,还提供了针对不同日志框架的适配器,对不同日志框架的接口进行二次封装,适配成统一的SLF4J接口定义

  • 适配器模式在JavaIO中的应用

    Reader、Writer接口的不同实现可以将对应的InputStream、OutputStream适配为Reader、Writer接口

装饰者模式

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承,可以在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。

public interface IA{
    void f();
}

public class A implements IA{
    public void f(){}
}
public class Decorator implements IA{
    private IA a;
    public Decorator(IA a){
        this.a = a;
    }
    public void f(){
        //功能增强代码
        a.f();
        //功能增强代码
    }
}

使用场景

  1. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。

  2. 需要增加有一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。

  3. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

应用解析

为了避免代码重复(对比通过继承实现),Java IO抽象出了一个装饰器父类FilterInpuetStream,InputSteam的所有的装饰器类(BufferedInputStream、DataInputStream)都继承自这个装饰器父类。

单例模式

保证一个类中仅有一个实例,并且提供一个访问他的全局访问点。

public class Client{
    public static void main(String[] args){
        //Singleton singleton = new Singleton();
        Singleton singleton = Singleton.instance();
    }
}

class Singleton{
    //private static Singleton singleton = new Singleton();//或者
    private static Singleton singleton;
    private Singleton(){

    }
    public static Singleton instance(){
        //return singleton;//或者
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

简单工厂设计模式

实现对象创建逻辑与对象使用方的解耦

public class Client{
    public static void main(String[] args){
        Product product = Factory.createProduct(1);
    }
}
class Factory{
    public static Prduct createProduct(int producrtType){
        switch(productType){
            case 1:
                return new ProductA();
            case 2:
                return new ProductB();
            default:
                return null;
        }
    }
    public static Product createProductA(){
        return new ProductA();
    }
    public static Product createProductB(){
        return new ProductB();
    }
}

abstract class Product{}
class ProductA extends Product{}
class ProductB extends Product{}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值