【Java】Java常用的设计模式

一、创建型模式

1. 单例模式

        单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

1.1 饿汉式

        类加载时就初始化,线程安全。

// 构造方法私有化
private Singleton() {}
 
// 饿汉式创建单例对象
private static Singleton singleton = new Singleton();
 
public static Singleton getInstance() {
	return singleton;
}

1.2 懒汉式

        第一次调用才初始化,避免内存浪费。

//由于懒汉式是非线程安全, 所以加上线程锁保证线程安全
private static Singleton singleton;
 
public static synchronized Singleton getInstance() {
	if (singleton == null) {
		singleton = new Singleton();
	}
	return singleton;
}

1.3 双重检验锁

        安全且在多线程情况下能保持高性能。

private volatile static Singleton singleton;

private Singleton (){}

public static Singleton getInstance() {
    if (singleton == null) {
        synchronized (Singleton.class) {
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

1.4 容器式单例

        当程序中的单例对象非常多的时候,则可以使用容器对所有单例对象进行管理,如下:

public class ContainerSingleton {
    private ContainerSingleton() {}
    private static Map<String, Object> singletonMap = new ConcurrentHashMap<>();
    public static Object getInstance(Class clazz) throws Exception {
        String className = clazz.getName();
        // 当容器中不存在目标对象时则先生成对象再返回该对象
        if (!singletonMap.containsKey(className)) {
            Object instance = Class.forName(className).newInstance();
            singletonMap.put(className, instance);
            return instance;
        }
        // 否则就直接返回容器里的对象
        return singletonMap.get(className);
    }
    public static void main(String[] args) throws Exception {
        SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
        SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
        System.out.println(instance1 == instance2); // true
    }
}

1.5 总结

适用场景:

  • 需要确保在任何情况下绝对只需要一个实例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。

优点:

  • 在内存中只有一个实例,减少了内存开销。
  • 可以避免对资源的多重占用。
  • 设置全局访问点,严格控制访问。

缺点:

  • 没有接口,扩展困难。
  • 如果要扩展单例对象,只有修改代码,没有其它途径。

2. 工厂方法模式(Factory Method)

2.1 简单工厂模式

        简单工厂模式不是23种设计模式之一,他可以理解为工厂模式的一种简单的特殊实现。

interface Fruit {
     public abstract void eat();
}
class Apple implements Fruit {
    public void eat(){
        System.out.println("Apple");
    }
}
class Orange implements Fruit {
    public void eat(){
        System.out.println("Orange");
    }
}
class Factory {
    public static Fruit getInstance(String ClassName) {
        Fruit f=null;
        try {
            //反射机制
            f=(Fruit)Class.forName(ClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}
class Client {
    public static void main(String[] a) {
        Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
        if(f!=null){
            f.eat();
        }
    }
}
2.1.1 总结

适用场景

  • 工厂类负责创建的对象较少
  • 客户端只需要传入工厂类的参数,对于如何创建的对象的逻辑不需要关心。

优点

  • 只需要传入一个正确的参数,就可以获取你所需要的对象,无须知道创建的细节。

缺点

  • 工厂类的职责相对过重,增加新的产品类型的时需要修改工厂类的判断逻辑,违背了开闭原则。
  • 不易于扩展过于复杂的产品结构。

2.2 工厂方法模式

        工厂方法模式是指定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。

工厂方法模式主要有以下几种角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它和具体工厂之间一一对应。
// 抽象产品
public interface Coffee {
}
// 具体产品
public class Americano implements Coffee {
}
public class Mocha implements Coffee {
}
public class Cappuccino implements Coffee {
}

// 抽象工厂
public interface CoffeeFactory {
    Coffee create();
}
// 具体工厂
public class AmericanoFactory implements CoffeeFactory {
    @Override
    public Coffee create() {
        return new Americano();
    }
}
public class MochaFactory implements CoffeeFactory {
    @Override
    public Coffee create() {
        return new Mocha();
    }
}
public class CappuccinoFactory implements CoffeeFactory {
    @Override
    public Coffee create() {
        return new Cappuccino();
    }
}
2.2.1 总结

适用场景

  • 创建对象需要大量的重复代码。
  • 客户端(应用层)不依赖于产品类实例如何被创建和实现等细节。
  • 一个类通过其子类来指定创建哪个对象。

优点

  • 用户只需要关系所需产品对应的工厂,无须关心创建细节。
  • 加入新产品符合开闭原则,提高了系统的可扩展性。

缺点

  • 类的数量容易过多,增加了代码结构的复杂度。
  • 增加了系统的抽象性和理解难度。

3. 建造者模式

        建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。

建造者(Builder)模式包含如下角色:

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
//产品类 电脑
@Data
public class Computer {
    private String motherboard;
    private String cpu;
    private String memory;
    private String disk;
    private String gpu;
    private String power;
    private String heatSink;
    private String chassis;
}
// 抽象 builder类(接口) 组装电脑
public interface ComputerBuilder { 
    Computer computer = new Computer();
    void buildMotherboard();
    void buildCpu();
    void buildMemory();
    void buildDisk();
    void buildGpu();
    void buildHeatSink();
    void buildPower();
    void buildChassis();
    Computer build();
}
// 具体 builder类 华硕ROG全家桶电脑(手动狗头)
public class AsusComputerBuilder implements ComputerBuilder {
    @Override 
    public void buildMotherboard() {
        computer.setMotherboard("Extreme主板");
    }
    @Override
    public void buildCpu() {
        computer.setCpu("Inter 12900KS");
    }
    @Override
    public void buildMemory() {
        computer.setMemory("芝奇幻峰戟 16G*2");
    }
    @Override
    public void buildDisk() {
        computer.setDisk("三星980Pro 2T");
    }
    @Override
    public void buildGpu() {
        computer.setGpu("华硕3090Ti 水猛禽");
    }
    @Override
    public void buildHeatSink() {
        computer.setHeatSink("龙神二代一体式水冷");
    }
    @Override
    public void buildPower() {
        computer.setPower("雷神二代1200W");
    }
    @Override
    public void buildChassis() {
        computer.setChassis("太阳神机箱");
    }
    @Override
    public Computer build() {
        return computer;
    }
}
// 指挥者类 指挥该组装什么电脑
@AllArgsConstructor
public class ComputerDirector {
    private ComputerBuilder computerBuilder;
    public Computer construct() {
        computerBuilder.buildMotherboard();
        computerBuilder.buildCpu();
        computerBuilder.buildMemory();
        computerBuilder.buildDisk();
        computerBuilder.buildGpu();
        computerBuilder.buildHeatSink();
        computerBuilder.buildPower();
        computerBuilder.buildChassis();
        return computerBuilder.build();
    }
}

   // 测试
    public static void main(String[] args) {
        ComputerDirector computerDirector = new ComputerDirector(new AsusComputerBuilder());
        // Computer(motherboard=Extreme主板, cpu=Inter 12900KS, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱)
        System.out.println(computerDirector.construct());
    }

3.1 总结

适用场景

  • 适用于创建对象需要很多步骤,但是步骤顺序不一定固定。
  • 如果一个对象有非常复杂的内部结构(属性),把复杂对象的创建和使用进行分离。

优点

  • 封装性好,创建和使用分离。
  • 扩展性好,建造类之间独立、一定程度上解耦。

缺点

  • 产生多余的Builder对象。
  • 产品内部发生变化,建造者都要修改,成本较大。

与工厂模式的区别

  • 建造者模式更注重方法的调用顺序,工厂模式更注重创建对象。
  • 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。
  • 关注点不同,工厂模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象由哪些部件组成。
  • 建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。

与抽象工厂模式的区别

  • 抽象工厂模式实现对产品族的创建,一个产品族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
  • 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
  • 建造者模式所有函数加到一起才能生成一个对象,抽象工厂一个函数生成一个对象

二、结构型模式

1. 代理模式

        代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。

        Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

代理(Proxy)模式分为三种角色:

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

1.1 静态代理

        静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口

// 火车站接口,有卖票功能
public interface TrainStation {
    void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
    @Override
    public void sellTickets() {
        System.out.println("广州火车站卖票啦");
    }
}
// 代售点卖票(代理类)
public class ProxyPoint implements TrainStation {
    // 目标对象(代理火车站售票)
    private TrainStation station = new GuangzhouTrainStation();
    @Override
    public void sellTickets() {
        System.out.println("代售加收5%手续费");
        station.sellTickets();
    }
}

    // 测试
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        // 代售加收5%手续费
        // 火车站卖票啦
        proxyPoint.sellTickets();
    }

        可以从上面代码看到,我们访问的是ProxyPoint对象,也就是说ProxyPoint是作为访问对象和目标对象的中介的,同时也对sellTickets方法进行了增强(代理点收取加收5%手续费)。

        静态代理的优点是实现简单,容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。

        而缺点也比较明显,那就是代理类和目标类必须有共同接口(父类),并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整。

1.2 动态代理

        代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在Java代码中定义好的,而是运行时由JVM动态生成,并且可以代理多个目标对象。

1.2.1 JDK动态代理

        代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在Java代码中定义好的,而是运行时由JVM动态生成,并且可以代理多个目标对象。

// 火车站接口,有卖票功能
public interface TrainStation {
    void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
    @Override
    public void sellTickets() {
        System.out.println("广州火车站卖票啦");
    }
}
// 深圳火车站卖票
public class ShenzhenTrainStation implements TrainStation {
    @Override
    public void sellTickets() {
        System.out.println("深圳火车站卖票啦");
    }
}
// 代售点卖票(代理类)
public class ProxyPoint implements InvocationHandler {
    private TrainStation trainStation;
    public TrainStation getProxyObject(TrainStation trainStation) {
        this.trainStation = trainStation;
        Class<? extends TrainStation> clazz = trainStation.getClass();
        return (TrainStation) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代售火车票收取5%手续费");
        return method.invoke(this.trainStation, args);
    }
}

    // 测试
    public static void main(String[] args) {
        ProxyPoint proxy = new ProxyPoint();
        TrainStation guangzhouTrainStation = proxy.getProxyObject(new GuangzhouTrainStation());
        // 代售火车票收取5%手续费
        // 广州火车站卖票啦
        guangzhouTrainStation.sellTickets();
        TrainStation shenzhenTrainStation = proxy.getProxyObject(new ShenzhenTrainStation());
        // 代售火车票收取5%手续费
        // 深圳火车站卖票啦
        shenzhenTrainStation.sellTickets();
    }

优点

  • 使用简单、维护成本低。
  • Java原生支持,不需要任何依赖。
  • 解决了静态代理存在的多数问题。

缺点

  • 由于使用反射,性能会比较差。
  • 只支持接口实现,不支持继承, 不满足所有业务场景。

1.2.2 CGLIB动态代理

        CGLIB是一个强大的、高性能的代码生成库。它可以在运行期扩展Java类和接口,其被广泛应用于AOP框架中(Spring、dynaop)中, 用以提供方法拦截。CGLIB比JDK动态代理更强的地方在于它不仅可以接管Java接口, 还可以接管普通类的方法。

        <!-- 先引入cglib包 -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>${cglib-version}</version>
        </dependency>
// 代售点卖票(代理类)
public class ProxyPoint implements MethodInterceptor {
    public TrainStation getProxyObject(Class<? extends TrainStation> trainStation) {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer =new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(trainStation);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象并返回
        return (TrainStation) enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代售火车票收取5%手续费");
        return methodProxy.invokeSuper(o, objects);
    }
}
    // 测试
    public static void main(String[] args) {
        ProxyPoint proxy = new ProxyPoint();
        TrainStation guangzhouTrainStation = proxy.getProxyObject(GuangzhouTrainStation.class);
        // 代售火车票收取5%手续费
        // 广州火车站卖票啦
        guangzhouTrainStation.sellTickets();
        TrainStation shenzhenTrainStation = proxy.getProxyObject(ShenzhenTrainStation.class);
        // 代售火车票收取5%手续费
        // 深圳火车站卖票啦
        shenzhenTrainStation.sellTickets();
    }

1.3 总结

应用场景

  • 保护目标对象。
  • 增强目标对象。

优点

  • 代理模式能将代理对象与真实被调用的目标对象分离。
  • 一定程度上降低了系统的耦合程度,易于扩展。
  • 代理可以起到保护目标对象的作用。
  • 增强目标对象的职责。

缺点

  • 代理模式会造成系统设计中类的数目增加。
  • 在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢。
  • 增加了系统的复杂度。

两种动态代理的对比

  • JDK动态代理的特点:
    • 需要实现InvocationHandler接口, 并重写invoke方法。
    • 被代理类需要实现接口, 它不支持继承。
    • JDK 动态代理类不需要事先定义好, 而是在运行期间动态生成。
    • JDK 动态代理不需要实现和被代理类一样的接口, 所以可以绑定多个被代理类。
    • 主要实现原理为反射, 它通过反射在运行期间动态生成代理类, 并且通过反射调用被代理类的实际业务方法。
  • cglib的特点:
    • cglib动态代理中使用的是FastClass机制。
    • cglib生成字节码的底层原理是使用ASM字节码框架。
    • cglib动态代理需创建3份字节码,所以在第一次使用时会比较耗性能,但是后续使用较JDK动态代理方式更高效,适合单例bean场景。
    • cglib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

三、参考博客

https://blog.csdn.net/pyy542718473/article/details/127248128

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值