IoC与DI工厂、单例、原型模式详解

1、工厂模式

1.1 工厂模式的由来

在现实生活中我们都知道

  • 原始社会自给自足(没有工厂)
  • 农耕社会有了小作坊(简单工厂,如民间酒坊)
  • 工业革命后有了流水线(工厂方法,自产自销)
  • 现代产业链中有代工厂(抽象工厂,如富士康)

我们的项目代码同样也是由简到繁一步一步迭代而来的,但对于调用者来说却越来越简单化了

1.2 简单工厂模式

简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于GoF的23种设计模式。
简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心。

举例:

1、假设现在开设有Java架构、大数据、人工智能等课程。
2、我们有一个课程标准ICourse接口:
3、创建一个Java课程的实现类:

public interface ICourse {
    /*录制视频*/
    public void record();
}
package SimpleFactoryPattern;
public class JavaCourse implements ICourse {

    @Override
    public void record() {
        System.out.println("录制Java课程");
    }
}
public class Project1Test {
    public static void main(String[] args) {
        ICourse course = new JavaCourse();
        course.record();
    }
}

如果要进行业务扩展,增加PythonCourse,会怎么样?

public class PythonCourse implements ICourse {
    @Override
    public void record() {
        System.out.println("录制python课程");
    }
}

按照刚才方式,如果业务扩展,则需要继续增加PythonCourse甚至更多课程类,则客户端的依赖会变得越来越臃肿。
因此,可用简单工厂模式对其进行优化,把课程的创建细节隐藏起来,减弱客户端与其他类的依赖。
优化方案(创建工厂类CourseFactory):

/**
 * 创建工厂类
 */
public class CourseFactory {
    public ICourse create(String name){
        if ("java".equals(name)){
            return new JavaCourse();
        }else if ("python".equals(name)){
            return new PythonCourse();
        }else {
            return null;
        }
    }
public class Project2Test {
    //创建工厂类客户端调用方法
    public static void main(String[] args) {
        CourseFactory factory = new CourseFactory();
        factory.create("java");
        factory.create("python");
    }
}

但这种方式下,如果业务继续扩展,就要修改工厂中的create方法,不符合开闭原则。
继续对其进行优化(反射技术):

public class CourseFactory {
    public ICourse create(String className){
        try {
            if (!(null == className || "".equals(className))){
                return (ICourse)Class.forName(className).newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

        /*
    public ICourse create(Class<? extends ICourse> clazz){
        try {
            if(null != clazz){
                return clazz.newInstance();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

     */
}
public class Project3Test {
    public static void main(String[] args) {
        CourseFactory factory = new CourseFactory();
        ICourse course1 = factory.create("SimpleFactoryPattern.JavaCourse");
        course1.record();
        ICourse course2 = factory.create("SimpleFactoryPattern.PythonCourse");
        course2.record();
    }
    /*
    public static void main(String[] args) {
        CourseFactory factory = new CourseFactory();
        ICourse course1 = factory.create(JavaCourse.class);
        course1.record();
        ICourse course2 = factory.create(PythonCourse.class);
        course2.record();
    }

     */
}

在这里插入图片描述
使用这种方式

  • 如果进行业务扩展,无需修改create方法代码,符合开闭原则。
  • 只能把ICourse及其子类作为参数传入工厂类,可避免一些未知错误。

1.3 工厂方法模式

简单工厂模式缺点:工厂类的职责过重,不易于扩展过于复杂的产品结构。若每个课程的创建逻辑有区别,随着产品增多,会变得更不易维护。
工厂方法模式(Factory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。
即:创建工厂抽象类,实现多个工厂,不同工厂用来实例化不同类型对象。

优点:可以解决产品扩展的问题。即根据“单一职责原则”,对职能进行拆分,Java课程由Java工厂创建,Python课程由Python工厂创建。
缺点:类的个数容易过多,增加复杂度。

举例:

以“简单工厂模式”中的例子进行拓展

public interface ICourseFactory {
    ICourse create();
}
public class JavaCourseFactory implements ICourseFactory {
    @Override
    public ICourse create() {
        return new JavaCourse();
    }
}
public class PythonCourseFactory implements ICourseFactory {
    @Override
    public ICourse create() {
        return new PythonCourse();
    }
}
public class FactoryTest {
    public static void main(String[] args) {
        ICourseFactory factory = new PythonCourseFactory();
        ICourse course = factory.create();
        course.record();

        factory = new JavaCourseFactory();
        course = factory.create();
        course.record();
    }
    /**
     * 优点:
     * 1.创建对象需要大量重复的代码
     * 2.客户端(应用层)不依赖于产品类的实例如何被创建、如何被实现等细节
     * 3.一个类通过其子类来指定创建哪个对象
     * 缺点:
     * 1.类的个数容易过多,增加复杂度
     * 2.增加了系统的抽象性和理解难度
     */
}

在这里插入图片描述

1.4 抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是指提供一个一系列相关或相互依赖对象的接口,无须指定它们的具体类。

举例

现在有多门课程,每门课程都有对应的笔记、视频等资料。若使用工厂方法模式,怎么进行设计?

Java课程Java笔记Java视频
Python课程Python笔记Python视频
课程笔记课程视频

则需要创建“Java笔记工厂”、“Python笔记工厂”、“Java视频工厂”、“Python视频工厂”等,类的数量太多。
抽象工厂模式,可通过为两个产品族(Java课程、Python课程)创建工厂类,并在每个工厂类中描述两个产品等级(笔记、视频),来解决多维度对象的创建问题。
1、创建两个产品等级的接口INote和IVideo

public interface INote {
    void edit();
}
public interface IVideo {
    void record();
}

2、创建一个抽象工厂类CourseFactory

/**
 * 抽象工厂类CourseFactory
 */
public interface CourseFactory {
    /**
     * 抽象工厂是主用户的主入口
     * 是Spring中应用得最广泛得一种设计模式
     * 易于扩展
     */
    INote createNote();
    IVideo createVideo();
}

3、创建Java产品族的视频类JavaVideo和笔记类JavaNote

/**
 * Java产品族的课堂笔记类JavaNote
 */
public class JavaNote implements INote {
    @Override
    public void edit() {
        System.out.println("编写Java笔记");
    }
}
/**
 * Java产品族的视频类JavaVideo
 */
public class JavaVideo implements IVideo {
    @Override
    public void record() {
        System.out.println("录制Java视频");
    }
}

4、创建Java产品族的具体工厂JavaCourseFactory

public class JavaCourseFactory implements CourseFactory {
    @Override
    public INote createNote() {
        return new JavaNote();
    }

    @Override
    public IVideo createVideo() {
        return new JavaVideo();
    }
}

5、创建Python的工厂类,视频类和笔记类,与Java的创建方式类似

/**
 * Python产品族的课堂笔记类PythonNote
 */
public class PythonNote implements INote {
    @Override
    public void edit() {
        System.out.println("编写Python笔记");
    }
}

/**
 * Ptthon产品族的视频类PythonVideo
 */
public class PythonVideo implements IVideo {
    @Override
    public void record() {
        System.out.println("录制Python视频");
    }
}

public class PythonCourseFactory implements CourseFactory {
    @Override
    public INote createNote() {
        return new PythonNote();
    }

    @Override
    public IVideo createVideo() {
        return new PythonVideo();
    }
}

6、客户端调用程序

public class AbstractTest {
    public static void main(String[] args) {
        JavaCourseFactory factory = new JavaCourseFactory();
        factory.createNote().edit();
        factory.createVideo().record();

        PythonCourseFactory factory1 = new PythonCourseFactory();
        factory1.createNote().edit();
        factory1.createVideo().record();
    }
}

缺点:扩展产品等级(如增加“源码”)时,会不符合开闭原则。
在这里插入图片描述

2、单例模式详解

单例模式是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

2.1 饿汉式单例模式

饿汉式单例模式,在类加载的时候就立即初始化,并且创建单例对象。
绝对线程安全,在线程还没出现之前就实例化了,不可能存在访问安全问题。
优点:没有加任何锁、执行效率高,用户体验比懒汉式好。
缺点:类加载的时候就初始化,不管用不用都占着空间,浪费了内存资源。
Spring中的IoC容器ApplicationContext就是饿汉式单例模式。

举例:

/**
 * 饿汉单例模式
 */
public class HungrySingleton {
    //先静态、后动态
    //先属性、后方法
    //先上后下
    public static final HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton(){
        System.out.println("构造方法");
    }
//先加载静态属性及代码块,在加载构造方法和非静态
    private static HungrySingleton getInstance(){
        System.out.println("getInstance");
        return hungrySingleton;
    }
}
//饿汉式适用于单例对象较少的情况
//饿汉式静态单例模式
//利用的是静态代码块机制
public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySingleton;
    static {
        hungrySingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){}
    public static HungryStaticSingleton getInstance(){
        return hungrySingleton;
    }
}
//优点:没有佳任何锁、执行效率高,用户体验比懒汉式单例模式更好
//缺点:类加载的时候久初始化,不管用于不用都占用空间,浪费了内存

2.2 懒汉式单例模式(双重检查锁)

懒汉式单例模式的特点:被外部类调用的时候内部类才会加载。
简单实现:静态方法只有在被调用时才会加载。

/**
 * 懒汉单例模式
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton() {}
    //静态模块,公共内存区域
        private static LazySimpleSingleton lazy = null;
        public static LazySimpleSingleton getInstance () {
            if (lazy == null) {
                lazy = new LazySimpleSingleton();
            }
            return lazy;
        }
    }

但是这种方式存在线程安全问题,可以通过下面代码进行验证:

/**
 * 线程类
 */
public class ExectorThread implements Runnable {

    @Override
    public void run() {
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":"+singleton);
    }
}
public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());

        t1.start();
        t2.start();
        System.out.println("End");
    }
}
//两个线程获取到的LazySimpleSingleton对象会出现不一致的可能

如何解决懒汉式的线程不安全的问题?
可以给getInstance()方法加上Synchronized(线程同步)关键字,使方法变成同步线程方法。

public class LazySimpleSingleton {
    private LazySimpleSingleton() {}
    //静态模块,公共内存区域
        private static LazySimpleSingleton lazy = null;
    //用Synchronized修饰后,若一个线程调用了此方法,其他线程就不能够同时调用
        public Synchronized static LazySimpleSingleton getInstance () {
            if (lazy == null) {
                lazy = new LazySimpleSingleton();
            }
            return lazy;
        }
    }

但这种方式下,每次执行getInstance方法都会进行线程同步。若线程数量较多,则CPU分配压力就会大大上升,导致大批线程阻塞,性能就会大幅下降!
怎么解决Synchronized引起的性能问题?
使用双重检查锁

public class LazySimpleSingleton {
    private LazySimpleSingleton() {}
        private static LazySimpleSingleton lazy = null;
        public static LazySimpleSingleton getInstance () {
            if (lazy == null) {
                Synchronized(LazySimpleSingleton.class){
                if (lazy == null) {
                lazy = new LazySimpleSingleton();//这不是一个原子指令,分为以下三步
                //1、分配内存给这个对象
                //2、初始化对象
                //3、设置lazy指向刚分配的内存地址
                }
            }
            }
            return lazy;
        }
    }

在这里插入图片描述
此种情况下,只有第一次调用getInstance方法时,才会进行线程同步,减少资源浪费。

3、原型模式详解

3.1 原型模式的应用场景

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过复制这些原型,创建新的对象。
主要适用于以下场景:
(1)类初始化消耗资源较多。
(2)使用new生成一个对象需要非常繁琐的过程(数据准备、访问权限等)。
(3)构造方法比较复杂。
(4)在循环体中产生大量的对象。
在Spring中,原型模式应用得非常广泛。例如使用IoC容器创建Bean对象时,可设置bean对象的scope作用域为prototype,来为同一个类创建多个不同的bean对象。

3.2 浅克隆

浅克隆:只复制了值类型数据,没有复制引用对象。
即:所有的引用对象仍然指向原来的对象。

举例:

public class JinGuBang implements Serializable {
    public float h = 100;
    public float d = 10;
}
public class Monkey {
    public int height;
    public int weight;
}
public class QiTianDaShengShallow extends Monkey implements Cloneable, Serializable {
    public JinGuBang jinGuBang;
    public QiTianDaShengShallow(){
        this.jinGuBang = new JinGuBang();
    }
    @Override
    protected  Object clone() throws CloneNotSupportedException{
        return this.shallowClone();
    }

    private QiTianDaShengShallow shallowClone(){
        QiTianDaShengShallow qi = new QiTianDaShengShallow();
        qi.height = this.height;
        qi.weight = this.weight;
        qi.jinGuBang = this.jinGuBang;
        return qi;
    }
}
public class ShallowCloneTest {
    public static void main(String[] args) {
        QiTianDaShengShallow qi = new QiTianDaShengShallow();
        try{
            QiTianDaShengShallow clone = (QiTianDaShengShallow) qi.clone();
            System.out.println("浅克隆:" + (qi.jinGuBang == clone.jinGuBang));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

在这里插入图片描述
使用浅克隆,获取到的jinGuBang属性依然指向原来的对象。

3.3深克隆

深克隆,不只复制了值,而且也复制了引用对象。
可通过多重循环遍历的方式,逐层迭代复制引用对象的值(自己试着实现)。
也可使用串行化输入输出流的方式实现。

举例:

public class QiTianDaShengDeep extends Monkey implements Cloneable, Serializable {
    public JinGuBang jinGuBang;
    public QiTianDaShengDeep(){
        this.jinGuBang = new JinGuBang();
    }
    @Override
    protected Object clone() throws CloneNotSupportedException{
        return this.deepClone();
    }
    private QiTianDaShengDeep deepClone(){
        try {
            //将要复制的本对象,串行化为二进制数据
            //此时会将对象中包含的对象也进行串行话
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            //进行深克隆:将二进制数据反串行化为对象实现
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            QiTianDaShengDeep qi = (QiTianDaShengDeep) ois.readObject();
            return qi;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
public class DeepCloneTest {
    public static void main(String[] args) {
        QiTianDaShengDeep qi = new QiTianDaShengDeep();
        try{
            QiTianDaShengDeep clone = (QiTianDaShengDeep) qi.clone();
            System.out.println("深克隆:"+ (qi.jinGuBang == clone.jinGuBang));
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
通过执行上面测试类可知:
使用深克隆后,克隆前后的jinGuBang属性不再指向同一对象。
深克隆会破坏单例,可通过以下方式防止:
1、不实现Cloneable接口;
2、重写clone方法,直接让其返回单例对象。

总结

简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心。
在工厂方法模式中用户只需要关心所需商品对应的工厂,无须关心创作细节,而且加入新的产品时符合开闭原则。
抽象工厂模式,是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们的具体类。
单例模式可以保证内存里只有一个实例,减少了内存的开销,还可以避免对资源的多重占用。
原型模式适用场景:

  1. 类初始化消耗资源较多
  2. 使用new生成一个对象需要非常繁琐的过程(数据准备、访问权限等)
  3. 构造函数复杂
    在循环体中含有大量的对象
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值