Java 单例模式、工厂模式、代理模式

单例模式

概念

单例模式指在内存中创建对象且仅创建一次的设计模式。在程序中多次使用同一对象且作用相同时,为了防止频繁地创建对象而是内存飙升,单例模式可以让程序仅在内存中创建一个对象,所有需要调用它的地方都共享这同一个对象。

单例模式的类型

懒汉式:在真正需要使用对象时,才去创建该单例对象。

懒汉式使用对象的方法是:在程序使用对象前先判断对象是否已经实例化,若已实例化则直接返回该类对象,否则先执行实例化对象操作。根据这个流程,可以写出一个懒汉式单例模式:

public class SingleDemo
{
	private static SingleDemo singleDemo;//静态属性
    
    private SingleDemo(){}//默认构造函数
    
    public static SingleDemo getInstance()//静态方法
    {
        if(singleDemo==null)
        {
            singleDemo=new SingleDemo();
        }
        return singleDemo;
    }
}

注意:这个例子是存在线程安全问题的,也就是当两个线程同时使用对象并且同时判空,那么这两个线程就会分别创建一个 SingleDemo 对象,这就不是单例了。最容易想到的方法就是加锁,可以在方法上加锁或者对类对象加锁:

public class SingleDemo
{
	private static SingleDemo singleDemo;
    
    private SingleDemo(){}
    //1.在方法上枷锁
    public static synchronized SingleDemo getInstance()
    {
        if(singleDemo==null)
        {
            singleDemo=new SingleDemo();
        }
        return singleDemo;
    }
}

public class SingleDemo
{
	private static SingleDemo singleDemo;
    
    private SingleDemo(){}
    
    public static SingleDemo getInstance()
    {//2.对类对象加锁
        synchronized(SingleDemo.class)
        {
            if(singleDemo==null)
        	{
            singleDemo=new SingleDemo();
        	}
        }
        return singleDemo;
    }
}

注意:规避双例情况的同时,会出现另一种问题,就是每次去获取对象都要先获取锁,并发性能较差,可能会出现卡顿。

改进:优化性能,如果未实例化对象过,就加锁,已经实例化过则不再加锁,直接获取实例。双重校验 + 加锁:

public class SingleDemo 
{
	private static SingleDemo singleDemo;
    
    private SingleDemo(){}
    
    public static SingleDemo getInstance()
    {
     	if(singleDemo==null)//先判空,再加锁
        {
            synchronized(SingleDemo.class)
            {
                if(singleDemo==null)//只有一个进程会进入该分支
                {
                    singleDemo=new SingleDemo();
                }
            }
        }
        return singleDemo;
    }
}

饿汉式:在类加载时,就已经创建好该单例对象,等待被程序使用,不需要等到被调用时再去创建。写一个饿汉式单例模式:

public class SingleDemo
{
    private static final SingleDemo singleDemo=new SingleDemo();
    
    private SingleDemo(){}
    
    public static SingleDemo getInstance()
    {
        return singleDemo;
    }
}

注意:饿汉式不存在并发安全与性能问题。

破坏单例模式

反射序列化都可以破坏单例对象,产生多个对象。

  1. 利用反射,强制访问类的私有构造器,创建一个新的对象。

    public static void main(String[] args)
    {
        Constructor<SingleDemo> construct=SingleDemo.class.getDeclaredConstructor();
        construct.setAccessible(true);
        SingleDemo obj=construct.newInstance();
        System.out.println(obj==SingleDemo.getInstance());
        //false
    }
    
  2. 利用序列化与反序列化破坏单例模式。

    public static void main(String[] args)
    {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("SingleDemo.file"));
        oos.writeObject(SingleDemo.getInstance());
        File file=new File("SingleDemo.file");
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
        SingleDemo newInstance=(SingleDemo)ois.readObject();
        System.out.println(newInstance==SingleDemo.getInstance());
        //false
    }
    

枚举实现单例模式

在 JDK 1.5 后,使用 Java 语言实现单例模式又多了一种方式:枚举

public enum SingleDemo
{
	INSTANCE;
    public void doSomething()
    {
        System.out.println();
	}
}

使用枚举的优势:代码简洁,没有做任何额外的处理,线程安全,可以防止反射、序列化与反序列化的破坏。

原因:枚举类默认继承 Enum 类,在利用反射调用 newInstance() 时,会判断该类是否是一个枚举类,如果是则抛出异常。

工厂模式

概述

Java 有三种工厂模式,提供了一种创建对象的最佳方式,这种类型的设计模式属于创建型模式,在创建对象时不会对客户端暴露创建逻辑,而是通过一个共同的接口来指向新创建的对象。

关键是在一个接口或抽象类中定义一个抽象方法并让实现该接口的类或抽象类的子类重写这个方法,返回某个子类的实例

简单工厂模式

提供一个创建对象实例的功能,而无需关心具体实现,被创建的类型可以是接口、抽象类,也可以是具体的类

例子

  1. 创建一个接口

    public interface Car{
        String getName();
    }
    
  2. 创建实现接口的实体类

    public class Benz implements Car{
        @Override
        public String getName(){
            return "Benz";
        }
    }
    
    public class BMW implements Car{
        @Override
        public String getName(){
            return "BMW";
        }
    }
    
  3. 创建普通类,简单工厂类,既能生产 Benz 又能生产 BMW

    public class SimpleFactory{
        public car getCar(String name){
            if(name.equals("Benz")) return new Benz();
            else if(name.equals("BMW")) return new BMW();
            else return null;
        }
    }
    
  4. 测试

    public class SimpleFactoryTest{
        public static void main(String[] args){
            SimpleFactory simpleFactory=new SimpleFactory();
            Car car=simpleFactory.getCar("Benz");
            System.out.pringtln(car.getName());
        }
    }
    
  5. 测试结果:Benz

总结:用户只要产品而不在乎产品生产过程,但是我们要在简单工厂里嵌套太多 if else 语句,并且繁杂的实际生产操作都要体现在工厂类中,不方便管理。

改进:每个品牌都应该有自己的生产类。

工厂方法

我们希望每个品牌都有自己的生产类,不同品牌的汽车由不同的工厂生产。

例子

  1. 创建一个工厂接口功能就是生产汽车

    public interface Factory{
        Car getCar();
    }
    
  2. Benz 工厂

    public class BenzFactory implements Factory{
        @Override
        public Car getCar(){
            return new Benz();
        }
    }
    
  3. BMW 工厂

    public class BMWFactory implements Factory{
        @Override
        public Car getCar(){
            return new BMW();
        }
    }
    
  4. 测试

    public class FactoryTest{
        public static void main(String[] args){
            Factory bmwFactory=new BMWFactory();
            System.out.println(bmwFactory.getCar().getName());
            Factory benzFactory=new BenzFactory();
            System.out.println(benzFactory.getCar().getName());
        }
    }
    
  5. 测试结果:BMW

改进:在测试类中,当用户想要不同品牌的车时,就要去对应的工厂生产,增加了用户的操作复杂性。

抽象工厂

简化测试类中的用户操作。

  1. 创建一个抽象类,抽象工厂类

    public abstract class AbstractFactory{
        protected abstract Car getCar();
        public Car getCar(String name){
            if("Benz".equalsIgnoreCase(name)) return new BenzFactory().getCar();
            else if("BMW".equalsIgnoreCase(name)) return new BMWFactory().getCar();
            else return null;
        }
    }
    
  2. 创建一个默认工厂

    public class DefaultFactory extends AbstractFactory{
        private DefaultFactory defaultFactory=new DefaultFactory();
        public Car getCar(){
            return defaultFactory.getCar();
        }
    }
    
  3. 测试类

    public class AbstractFactoryTest{
        public static void main(String[] args){
            DefaultFactory factory=new DefaultFactory();
            System.out.println(factory.getCar("Benz".getName()));
        }
    }
    
  4. 测试结果:Benz

总结:用户需要车,只要去找默认工厂提出自己的需求(传参),便能得到想要的产品,而不用根据产品去寻找不同的生产工厂。

代理模式 Proxy

概述

通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。

静态代理

代理类对象在程序运行时由开发人员手动创建,代理的目标类是固定的。优点是实现简单、易于理解,缺点是代理类依赖目标类,当目标类增加时,代理类可能需要成倍的增加,不易维护。

实现步骤

  1. 创建接口,定义方法
  2. 创建目标类,实现接口
  3. 创建代理类,实现接口并引入目标类对象
  4. 创建代理对象并执行业务方法

动态代理

代理类对象在程序运行时由 JVM 虚拟机动态创建,代理的目标类是可活动、可设置的。优点是代理类不依赖目标类,当目标类增加时,代理类不用成倍增加,代码简单,易于维护。缺点:实现复杂,难于理解。

实现步骤

  1. 创建接口,定义目标类要完成的功能
  2. 创建目标类实现接口
  3. 创建 InvocationHandler 接口实现类,在 invoke() 方法中完成代理类的功能
  4. 创建代理对象并把返回值转为接口类型
  5. 通过代理对象执行业务方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值