设计模式学习

参考文章:

设计模式的作用:

需求经常变更,而设计模式的存在就是为了抵御需求变更。

1、单例模式

1.1 单例模式的概念:

确保一个类只有一个实例,并提供该实例的全局访问方式。

1.2 单例模式的应用场景?

    有频繁创建对象然后销毁的情况
    创建对象耗时过多或者说消耗资源过多,但又经常使用该对象
    频繁访问IO资源或者数据库资源,例如:连接池,线程池对象的应用,注意,这里的单例对象是连接池和线程池而非连接池中的连接和线程池中的线程,我们通过连接池来管理所有的连接,方便了连接的管理,并且连接池一般只创建一次且创建一次的开销很大,后面一直使用这个连接池,符合单例模式的思想

1.3 单例模式应用的目的?

减少对象在内存中资源的占用
1.资源共享的情况下,避免由于操作资源时导致的性能或损耗等。如上述中的日志文件,应用配置。 
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。

1.4 单例模式的具体实现思路?

规则:
    类的外部不允许直接构建此类对象
    类的外部只能通过静态方法访问此对象
实现:
    将构造方法私有化
    在类的内部创建对象
    定义一个静态方法,通过这个方法直接返回此对象

1.5 实现方式:

饿汉式

优点:线程安全
缺点:无法实现延迟实例化,资源开销较大
// 饿汉式
public class Singleton1{
    private static Singleton1 instance = new Singleton1();
    
    private Singleton1(){}
    
    public static Singleton1 getInstance(){
        return instance;
    }
}

懒汉式

优点:可以实现“延迟实例化”。
缺点:线程不安全,无法保证在多线程场景中依然可以保持单例。
// 懒汉式
public class Singleton1 {
    // 声明一个静态变量
    private static Singleton1 instance;
    
    // 私有化构造方法
    private Singleton1(){}
    
    // 提供一个获取实例的静态方法
    public static Singleton1 getInstance(){
        if(instance == null){
            instance = new Singleton1();
        }
        return instance;
    }
}

改进版懒汉式

优点:既能实现延迟实例化又线程安全

缺点:同步一个方法,锁的粒度太大,存在性能问题

// 改进版的懒汉式
public class Singleton1{
    private static Singleton1 instance;

    private Singleton1(){}

    private static synchronized Singleton1 getInstance(){
        if(instance == null){
            instance = new Singleton1();
        }
        return instance;
    }
}

双锁检查的单例实型方式

优点:可以实现延迟实例化,且线程安全,且没有性能问题

缺点:可以通过反射来创建实例,不是特别安全

// 双锁检查的单例实型方式
public class Singleton1{
    private static volatile Singleton1 instance;
    
    private Singleton1(){}
    
    private static Singleton1 getInstance(){
        if(instance == null){
            synchronized(Singleton1.class){
                if(instance == null){
                    instance = new Singleton1();
                }
            }
        }
        return instance;
    }
}

静态内部类的实现方式

优点:由于静态内部类只会在调用 getInstance 方法是才会被加载进内存,而且只会被加载一次,所以可以实现延迟实例化,且线程安全

// 静态内部实现方式
public class Singleton1{
    private Singleton1(){}
    
    private static class SingletonHolder{
        private static Singleton1 instance = new Singleton1();
    }
    
    public static Singleton1 getInstance(){
        return SingletonHolder.instance;
    }
}

枚举实现单例模式

优点:编码简单,由于枚举类构造方法天生就是私有的,所以线程安全,最后还可以避免反射攻击。该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。
// 枚举实现单例方式
public enum Singleton1{
    instance;
    
    public static Singleton1 getInstance(){
        return instance;
    }
}

为什么枚举单例类可以避免反射攻击

反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举类不能通过反射来创建对象

扩展:枚举类如何实现线程安全:

通过反编译后,可以看到单例枚举类是一个final类,且创建该对象的实例是在一个static静态语句块中进行的,根据JVM的类加载机制,静态语句块只会在类被加载时执行一次,所以可以线程安全。另外因为单例枚举类反编译后实际上是一个被final修饰的类,所以他不能被继承,也就不能创建子类对象。
经过反编译后
public final class  EnumSingleton extends Enum< EnumSingleton> {
        public static final  EnumSingleton  ENUMSINGLETON;
        private static final Singleton ENUM$VALUES[];
 
        public static  EnumSingleton[] values();
        public static  EnumSingleton valueOf(String s);
        static {
            ENUM$VALUES = (new EnumSingleton[] {
                ENUMSINGLETON
            });
        };
}

单例模式存在的问题

1、初始化的线程安全问题
2、反序列化破坏单例
ObjectInputStream使用readObject的方法来实现对象的反序列化,readObject()方法会调用一个名为readOrdinaryObject的方法,该方法会通过反射的方式调用无参构造方法新建一个对象。
反序列化破坏单例
public class SerializableDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // Write to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Singleton.getSingleton());
        // Read from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        System.out.println(newInstance == Singleton.getSingleton());
    }
}
//false

解决办法:

1. 在单例类中增加一个 readResolve() 方法,方法中返回单例实例。因为 readOrdinaryObject() 方法在通过反射创建完对象后,会有一个条件判断,如果对象所在的类存在 readResolve() 方法,则会改为使用 readResolve() 方法获取实例对象,而不是利用反射创建出来的对象
import java.io.Serializable;
/**
     * 使用双重校验锁方式实现单例
     */
public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
 
    private Object readResolve() {
        return singleton;
    }
}
 
2. 使用枚举单例模式
 反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举类不能通过反射来创建对象
public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

简单工厂模式

在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

实现过程:

把实例化的操作单独放到一个工厂类中,这个类有一个方法,这个方法会根据形参的值返回不同的子类实例。这个类就称为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。

好处:

这样做能把用户类和具体子类的实现解耦,用户类不再需要知道有哪些子类以及应当实例化哪个子类。而只需要向工厂类提供一些必要的信息,就能从工厂中取得它想要的类实例。用户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如某个子类的类名发生改变或者增加了某个子类,那么所有的客户类都要进行修改。如果使用了简单工厂,用户类不需要知道子类的细节,并且就算子类发生改变,也只要在工厂类的创建对象方法中增加一个分支判断就可以 了。

缺点:

每次增加子类或者删除子类对象都需要对简单工厂类进行修改。不符合开闭原则,所谓的开闭原则就是对扩展开放,对修改封闭,就是最好不要修改工厂的方法,而是采用功能的扩展的方式来达到目的。另外,随着子类数量的增加,简单工厂类会变得很庞大臃肿、耦合性高,所以就引入了工厂方法设计模式。

 

代码实现

interface Product{

}

class ConcreteProduct implements Product{

}

class ConcreteProduct1 implements Product{

}

class ConcreteProduct2 implements Product{

}
public class SimpleFactory {
    public Product createProduct(int type){
        if(type == 1){
            return new ConcreteProduct1();
        }else if(type == 2){
            return new ConcreteProduct2();
        }else{
            return new ConcreteProduct();
        }
    }
}
    public static void main(String[] args) {
        SimpleFactory simpleFactory = new SimpleFactory();
        Product product = simpleFactory.createProduct(2);
        // do something
    }
 

工厂方法模式

工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。我们需要哪个类就创建哪个类的工厂,通过这个工厂来获取对象。

优点

1. 符合开闭原则,如果需要增加某个子产品,只需要增加一个创建该产品的产品工厂,这个产品工厂继承了抽象工厂,没有修改原有的代码。所以工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。
2. 工厂方法把实例化操作推迟到子类工厂,可以提高性能

缺点

随着产品数量的增加,具体产品工厂的会越来越多。

工厂方法模式代码:

interface Product{

}

class ConcreteProduct1 implements Product{

}

class ConcreteProduct2 implements Product{

}
abstract class Factory{
    public abstract Product createProduct();
}

class ConcreteFactory1 extends Factory{

    @Override
    public Product createProduct() {
        return new ConcreteProduct1();
    }
}

class ConcreteFactory2 extends Factory{
    @Override
    public Product createProduct() {
        return new ConcreteProduct2();
    }
}
public static void main(String[] args) {
        Factory concreteFactory1 = new ConcreteFactory1();
        Product product1 = concreteFactory1.createProduct();
        Factory concreteFactory2 = new ConcreteFactory2();
        Product product2 = concreteFactory2.createProduct();
    }

抽象工厂模式

抽象工厂模式是工厂方法模式的一个版本延伸,同样必须先有一个抽象工厂,这个工厂有很多子类产品工厂,但是这些产品工厂不再是只用来创建一个产品,而是用来创建一个配套系列的产品,创建一个产品系列家族。比如一个配套产品需要两个组件,组件1 和 组件 2, 如果我需要 A 套产品,我就只需要创建 A 工厂,A 工厂就可以为我提供 A1,  A2,两个组件, 如果我需要 B套产品,我就只需要创建 B工厂,B 工厂就可以为我提供 B1,  B2,两个组件,这样我们通过一个具体的工厂就可以获得一个产品链,一个配套系列的产品家族。
比如比如一个创建汽车的工厂,这个工厂有两个方法,一个方法用于创建空调对象,另一个方法用来创建一个没有空调的车对象。这样宝马工厂就用来创建宝马的空调,创建宝马的车体,奔驰工厂就用来创建适用于奔驰车的空调和奔驰车的车体。这样我们通过一个具体的工厂就可以获得一个产品链,一个配套系列的产品家族。

好处

 1. 抽象工厂模式最大的好处是易于交换产品系列,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,
2. 抽象工厂模式的另一个好处就是它封装了具体的创建实例过程,使得我们不用去管这些创建实例的细节,简化了开发,减少了代码量

缺点

1. 每次获取实例对象都要创建工厂,如果用户程序非常多,就可能需要创建非常多的具体工厂。
2. 如果我们需要在一个系列中增加一个产品,那么需要在顶级工厂中增加一个方法,从而需要改动所有的具体工厂。

抽象工厂模式代码实现:

产品类:
abstract class AbstractProduct1{

}
abstract class AbstractProduct2{

}

class ProductA1 extends AbstractProduct1{


}

class ProductB1 extends AbstractProduct1{

}

class ProductA2 extends AbstractProduct2{


}

class ProductB2 extends AbstractProduct2{

}
工厂类:
abstract class Factory{
    abstract public AbstractProduct1 createProduct1();
    abstract public AbstractProduct2 createProduct2();
}

class ProductAFactory extends Factory{
    @Override
    public AbstractProduct1 createProduct1() {
        return new ProductA1();
    }

    @Override
    public AbstractProduct2 createProduct2() {
        return new ProductA2();
    }
}
class ProductBFactory extends Factory{
    @Override
    public AbstractProduct1 createProduct1() {
        return new ProductB1();
    }

    @Override
    public AbstractProduct2 createProduct2() {
        return new ProductB2();
    }
}
public static void main(String[] args) {
        // 创建一个家族A产品
        Factory factoryA = new ProductAFactory();
        AbstractProduct1 A1 = factoryA.createProduct1();
        AbstractProduct2 A2 = factoryA.createProduct2();
    }

动态代理设计模式

就是在目标类的基础上增加切面逻辑,生成增强的目标类
动态的代理的实现方式主要有两种,分别是 CGLib 动态代理和 JDK动态代理
JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

区别:

JDK代理只能对实现接口的类生成代理,没有实现接口的类不能被代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,因为通过继承类的实现方式,所以不能代理final修饰的类。
cglib通过修改字节码生成子类,创建效率较低,执行效率高;jdk动态代理的方式创建代理对象效率较高,但是因为通过反射机制代理对象,所以执行效率较低,
jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势

CGlib 代码实现

class Actor2{

    public void basicPerform(float money){
        System.out.println("拿到钱 " + money + ",开始基本表演");
    }

    public void dangerPerform(float money){
        System.out.println("拿到钱 " + money + ", 开始危险表演");
    }
}
public class CGLibProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       String name = method.getName();
       Object rtValue = null;
       Float money = (Float)args[0];

       if("basicPerform".equals(name)){
           if(money > 2000){
               args[0] = money / 2;
               rtValue = methodProxy.invokeSuper(o, args);
           }
       }

       if("dangerPerform".equals(name)){
           if(money > 5000){
               args[0] = money / 2;
               rtValue = methodProxy.invokeSuper(o, args);
           }
       }
        return rtValue;
    }
}
class Client2{
    public static void main(String[] args) {
        CGLibProxy cglibProxy = new CGLibProxy();
        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Actor2.class);   // 设置Enhancer 的父类
        enhancer.setCallback(cglibProxy);

        // 创建代理对象
        Actor2  actor = (Actor2) enhancer.create();
        actor.basicPerform(3400);
        actor.dangerPerform(6500);
    }
}

CGLib 的匿名内部类实现

class Client2{
    public static void main(String[] args) {
        final Actor2 actor2 = new Actor2();
        // 创建代理对象
        Actor2 cglibActor = (Actor2)Enhancer.create(actor2.getClass(), new MethodInterceptor(){
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws InvocationTargetException, IllegalAccessException {
                String name = method.getName();
                Object rtValue = null;
                Float money = (Float)args[0];
                if("basicPerform".equals(name)){
                    // 基本演出
                    if(money > 2000){
                        rtValue = method.invoke(actor2, money / 2);
                    }
                }
                if("dangerPerform".equals(name)){
                    if(money > 5000){
                        rtValue = method.invoke(actor2, money / 2);
                    }
                }

                return rtValue;
            }
        });

        cglibActor.basicPerform(3000);
        cglibActor.dangerPerform(6500);
    }
}

jdk动态代理代码实现

interface IActor{
    // 基本表演
    public void basicPerform(float money);
    // 危险表演
    public void dangerPerform(float money);
}

class Actor implements IActor{
    @Override
    public void basicPerform(float money){
        System.out.println("拿到钱 " + money + ",开始基本表演");
    }
    @Override
    public void dangerPerform(float money){
        System.out.println("拿到钱 " + money + ", 开始危险表演");
    }
}
public class JDKProxy implements InvocationHandler{
    // 需要代理的对象
    public Object targetObject;
    /*public Object newProxy(Object targetObject){
        // 将目标对象传入进行代理
        this.targetObject = targetObject;
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
    }*/
    public JDKProxy(Object target){
        targetObject = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        Object rtValue = null;
        String name = method.getName();
        Float money = (Float)args[0];
        if("basicPerform".equals(name)){
            if(money > 2000){
                rtValue = method.invoke(targetObject, money / 2);
            }
        }
        if("dangerPerform".equals(name)){
            if(money > 5000){
                rtValue = method.invoke(targetObject, money / 2);
            }
        }
        return rtValue;
    }
}
class Client{
    public static void main(String[] args) {
        // 创建被代理对象
        IActor actor = new Actor();
        // 创建 Handler
        JDKProxy JDKInvocationHandler = new JDKProxy(actor);
        // 创建代理对象
        IActor actorProxy = (IActor) Proxy.newProxyInstance(actor.getClass().getClassLoader(),
                actor.getClass().getInterfaces(), JDKInvocationHandler);
        actorProxy.basicPerform(3000);
        actorProxy.dangerPerform(7000);
    }
}

JDK 动态代理的匿名内部类实现

 
原文地址:https://www.cnblogs.com/hi3254014978/p/14153559.html