JAVA常用的七种设计模式

鲁永福
2023-12-01

学习设计模式之前,我们先要了解一下设计模式的怎么来的?

对于设计人员,特别是开发人员吗,往往受限于眼界或经验不能够体会到设计原则的实用性,或者在处理具体问题时,不知道如何把设计原则应用到到设计和代码,因此产生了“模式”。
随着参与的项目越来越多,人们发现:很多问题并不是一个项目中出现的,它会在很多的项目中出现。于是人们就把这些问题总结出来,然后给出了解决这些问题的方案,而这些方案–“模式”(解决问题的套路)。

设计模式的分类

1.创建模式:创建一些特殊的对象,或者在特殊要求下创建对象。
2.结构模式:主要利用组合/聚合或者继承,让类与类能够形成某种关联关系 – 代理。
3.行为模式:刻画了类和对象交换及分配职责的方式。

接下来我们正式介绍七种常用的设计模式

单例模式

1.饿汉模式(最基本的单例模式)

类加载时,会直接实例化单例对象,以后都返回该对象的引用。

  • 缺点:类加载时,会直接实例化单例对象,不管是否使用到该单例对象,浪费内存。
  • 优点:没有枷锁,执行效率高,线程安全的实例。
	public class Singleton{
		private Singleton{
		}
		//创建本类的私有构造方法
		private static Singleton singleton = new Singleton();
		
		public static Singleton getInstance(){
			return singleton;
		}
	}

2.懒汉模式(线程不安全、线程安全但效率低)

不要直接在类加载时实例化,而是在调用方法时,再实例化。

  • 优点:不会占用内存
  • 缺点:安全方面 单线程情况下,是安全的,但是在多线程下,多个线程可能同时执行singleton == null 都为true,会创建多个实例在内存中。
	public class LazySingleton{
		private LazySingleton(){
		}
		
		private static LazySingleton singleton;
		
		public static LazySingleton getInstance(){
			if(singleton == null){
				singleton = new LazySingleton();
			}
			return singleton;
		}
	}

2.1懒汉模式(双重检验模式(线程安全,且效率高的) 把锁的粒度变小,只锁第一次初始化时)

  • 实例会在调用getInstance方法时创建,仅在第一调用初始化时需要锁住。
		public class LazySingleton{
		private LazySingleton(){
		}
		
		private static LazySingleton singleton;
		
		public static LazySingleton getInstance(){
		/*
            双重检验
            首先先判断实例是否为null,为null则使用synchronized锁住类,
            然后在同步块中,再一次判断实例是否为null,为null则初始化实例。
            synchronized(需要锁的对象){}
        */
			if(singleton == null){
				synchronized(LazySingleton .class){
					if(singleton == null){
						singleton = new LazySingleton();
					}
				}
			}
			return singleton;
		}
	}

3.内部类实现模式

通过静态内部类,完成单例模式的创建。

  • 在外部类加载时,并不会加载内部类,也就是不会执行new 实例(),这属于懒加载。
  • 只有第一次调用getInstance方法时,才会加载。
	public class InnerSingleton{
		private InnerSingleton(){
		}
		
		private static class Inner{
			private static InnerSingleton instance = new InnerSingleton();
		}
		
		public static InnerSingleton getInstance(){
			return Inner.instance;
		}
	}

4.枚举实现

通过枚举创建 单例模式。

  • 实现单例的最佳方法。简洁,支持自动序列化机制,防止多次实例化,但目前还没有被广泛采用。
	public class EnumSingleton{
		private EnumSingleton(){
			
		}
		
		private static enum SinEnum{
			//自定义的枚举值,如果没有该自定义枚举值,无法获取枚举对象
			SIN;
			private EnumSingleton es = new EnumSingleton();
		}
		
		public static EnumSingleton getInstance(){
			SinEnum s = SinEnum.SIN;
			return s.es;
		}
	}

工厂模式

讲使用者和对象的生产者进行分离。

在工厂模式中,几乎都有三种角色,工厂(抽象工厂、具体工厂) 产品(抽象产品、具体产品) 使用者。使用者想要使用产品,不用自己去生产产品,把生产的动作交给工厂去做,使用者只需要从工厂提供产品的位置(方法)去拿就好。

  • 1.简单工厂模式–顾客需要给出清单。
    变化点在产品对象上,所以我们会抽象产品,然后通过一个工厂,根据不同的情况产生不同的产品对象。

  • 2.工厂方法模式–根据工厂能产生什么顾客拿什么。
    工厂可以产生统一品牌的商品,会根据商品去抽象工厂,对每一个产品,提供一个工厂实现类。

  • 3.抽象工厂模式–根据工厂能产生什么顾客拿什么,但是工厂能产生的产品会有多种品牌。
    超级工厂,可以生产不同品牌的各种产品,抽象出超级工厂,也要抽象出产品,然后根据不同的品牌给出该品牌商品的工工厂实现类。

原型模式

根据一个已经存在的对象,创建一个和他一样的对象。-- 克隆

浅克隆-- 利用Object中clone()实现

1.让被克隆的类实现Cloneable接口。

2.重写clone方法,方法访问修饰符public。

3.对象.clone()的方式的到一个一样的对象。

浅克隆指,被克隆的对象会产生一个新的,但是对象属性不会产生。

	public class Man implements Cloneable {
		private String name;
		public Car car = new Car();
		
		public Man clone() throws CloneNotSupportedException{
			Object obj = super.clone();
			return (Man)obj;
		}
	}

深度克隆

1.克隆对象所涉及的自定义类,需要实现序列化接口。

2.在需要克隆的类中,添加一个方法,完成序列化反序列化即可。

	public class Man implements Cloneable,Serializable {
		private String name;
		public Car car = new Car();
			
		public Man depthClone() throws IOException, ClassNotFoundException {
			//获取对象信息,把当前对象写入另一块内存
	        ByteArrayOutputStream bo = new ByteArrayOutputStream();
	        ObjectOutputStream objOut = new ObjectOutputStream(bo);
	        objOut.writeObject(this);
	
	        //读取内存 创建对象
	        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
	        ObjectInputStream objIn = new ObjectInputStream(bi);
	        Object obj = objIn.readObject();
	        return (Man) obj;
			}
		}

代理模式

1.静态代理

根据目标对象需要代理的行为,抽象出一个接口(包含了需要代理的行为),目标类和代理类都需要去实现该接口,然后将目标对象注入到代理类中,此时就可以在代理类中调用目标对象的行为,并为止附加非功能性逻辑。

2.动态代理之JDK代理

  • 第一步,实现接口InvocationHandler,然后重写invoke方法,在invoke方法中调用目标对的方法。

  • 第二步,提供一个自定义的方法,通过Proxy.newProxyInstance()得到代理对象。

	public class LivePeople implements InvocationHandler {

    private IEat target;//目标对象

    public LivePeople(IEat target) {
        this.target = target;
    }

    //获取代理对象
    public Object getProxy() {
        //Proxy()类中的newProxyInstance()方法
        /*
            第一个参数:目标对象的类加载器
            第二个参数:目标对象实现的所有接口
            第三个参数:代理类的对象
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("订餐");
        Object returnInfo = method.invoke(target, args);//执行目标对象的方法
        System.out.println("结账,开发票");
        return returnInfo;
    }
}

3.动态代理之Cglib代理

  • 第一步,导入Cglib依赖(包)。

  • 第二步,实现接口MethodInterceptor,重写intercept方法,在其中完成目标对象的方法调用。

  • 第三步,提供自定义方法,通过工具类获取得到代理对象。

	public class CglibProxy implements MethodInterceptor {

    private IEat target;

    public CglibProxy(IEat target) {
        this.target = target;
    }

    //给目标对象创建代理对象(自定义)
    public Object getProxyInstance() {
        //创建工具栏对象
        Enhancer en = new Enhancer();
        //设置目标对象父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建代理对象
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("订餐");
        Object returnInfo = methodProxy.invokeSuper(o, objects);//调用目标对象的方法并传入参数
        System.out.println("付款,开发票");
        return returnInfo;
    }
}

装饰器模式

对象功能的扩展能够根据需要来动态地实现。

以咖啡举例:

  • 1.根据对象抽象一个公共的接口。

  • 2.根据接口给出不同的实现类(主料类(主料类产生的对象就是被装饰的对象) 和 配料类–装饰类)。

  • 3.在配料类中注入被装饰的对象。

  • 4.生产咖啡时,先生产主料(被修饰的对象),然后用配料不断去修饰主料。

适配器模式

使得原本不兼容的两个接口(功能)可以兼容 – 搭建了两个接口间的桥梁。

	class Tel{
    public void call(){}
	}
	class Carame{
	    public void make(){}
	}
	class Phone extends Tel{
	    private Carame c = new Carame();
	    public void make(){
	        c.make();
	    }
	}

实现适配器的方案,继承或者依赖(推荐使用)

优点:

  • 可以让没有任何关联的类,一起运行;

  • 提高了类的复用

  • 灵活性好

缺点:

  • 过多的使用适配器,会导致系统非常混乱,不容具体把控

  • java是单继承。

观察者模式

  • 主题类(由它产生的对象 就是 被观察的对象) 继承 Observable的类。
    在主题类,需要针对主题(价格的变化进行关注,设置变化点,然后通知观察者价格有了变化)。
	public class Product extends Observable {
    private double price;
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
        // 通知观察者注意到主题的变化
        this.setChanged();// 设置变化点
        this.notifyObservers(price);//通知观察者
    }
}
  • 观察者类(由它产生的对象 就是 观察者) 实现Observer接口。
    重写update(当主题发生变化时,会调用方法)。
	public class ProductProxy1 implements Observer {
    private double price;
    /**
     * 当主题类的值发生变化后,会调用该方法
     * @param o 主题对象
     * @param arg 主题更新的值对象
     */
    @Override
    public void update(Observable o, Object arg) {
        double factoryPrice = (double) arg;
        this.price = factoryPrice * 1.5;
    }
    public double getPrice() {
        return price;
    }
}
  • 首先产生主题对象(被观察对象),产生观察者对象,然后给主题对象设置观察者,最后通过更改主题的值,测试观察者是否有观测到主题值的改变。
	public class Test {
    public static void main(String[] args) {
        // 产生主题对象  -- 被观察者
        Product product = new Product();
        // 产生观察者
        ProductProxy1 p1 = new ProductProxy1();
        // 给主题对象添加观察者
        product.addObserver(p1);
        // 主题发生变化
        product.setPrice(1000);
        System.out.println("出厂价格:"+ product.getPrice() +
                           "\n代理商售卖价格:" + p1.getPrice());
        // 主题发生变化
        product.setPrice(2000);
        System.out.println("出厂价格:"+ product.getPrice() + 
                           "\n代理商售卖价格:" + p1.getPrice());
    }
}
 类似资料: