常见的设计模式【单例模式&工厂模式】

目录

学习设计模式的好处

单例模式

单例模式的概念

单例模式的使用场景

单例模式的优缺点

单例模式的分类

 1.饥饿模式

 2.懒汉模式(惰)

工厂模式

概念

使用原因

使用场景 


学习设计模式的好处

总而言之一句话:设计模式代表了最佳的实践,是很多优秀的软件开发人员的经验总结

设计模式(Design pattern)代表了最佳的实践,是很多优秀的软件开发人员的经验总结,是解决特定问题的解决方案。它并不是语法规定,也不拘泥于特定语言。 恰当的使用设计模式可以提高代码的可复用性,可维护性,可扩展性,健壮性及安全性,这些都是系统非常重要的非功能性需求。设计模式的广泛使用起始于1995年,GOF(四人帮)出版的《设计模式:可复用面向对象软件基础》。

理解一些基础的设计模式可以帮助你提高自己代码的复用性,灵活性

本期文章介绍的是单例模式,后期将继续讲解别的设计模式,有兴趣的可以关注一下🙂

单例模式

单例模式的概念

 “保证在内存中只用一个实例”

 也可以说:一个系统中,一个类只能有一个实例对象

你没有看错,单例模式的概念就是这么简单,不过想要真正弄懂它可不容易😰

单例模式的使用场景

比如:系统配置文件的管理,这些配置文件只要使用一个单例对象进行读写即可,系统在其他地方需要使用配置信息时,只要使用该单例对象进行获取就可以了,这样便于统一管理配置信息

单例模式的优缺点

 优点:

  • 在内存中只有一个对象,节省内存空间;
  • 避免频繁的创建销毁对象,可以提高性能;
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点,易于控制

 缺点:

  • 不适用于变化频繁的对象(适合多例模式);
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出

单例模式的分类

1.饥饿模式

饥饿模式:不管是否使用该实例都会生成实例。简单,且线程是安全的

/**
 * 单例模式,饥饿加载
 */
public class SingletonDemo {

    //1. 需要有一个私有的构造函数,防止该类通过new的方式创建实例(实例=对象)
    private SingletonDemo(){}

    //2. 饥饿模式,首先生成一个实例(不管是否使用该实例都会生成)
    private static final SingletonDemo instance = new SingletonDemo();

    //3. 静态方法,用于获取已经生成的实例
    public static SingletonDemo getInstance() {
        return instance;
    }

    /*
     * 测试多线程下单例模式是否安全
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(SingletonDemo01.getInstance().hashCode());
            }).start();
        }

}

 2.懒汉模式(惰)

懒汉模式(惰):只有在需要使用实例时才生成实例

但是在懒汉模式中存在着许多多线程的相关问题,线程问题不可忽视。为了带大家更好的了解,我整理了常见的几种写法进行总结归纳👇👇

  • 第一种写法(单例模式: 懒汉式, 有线程问题
/**
 * 单例模式: 懒汉式, 有线程问题
 */
public class SingletonDemo02 {

    private SingletonDemo02(){
        //模拟构造函数的运行耗时:在运行构造函数前暂停10毫秒
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static SingletonDemo02 singletonDemo02 = null;

    public static synchronized SingletonDemo02 getInstance() {

        if (singletonDemo02 == null) {

            singletonDemo02 = new SingletonDemo02();
        }

        return singletonDemo02;
    }

    /*
     * 测试多线程下单例模式是否安全
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(SingletonDemo02.getInstance().hashCode());
            }).start();
        }
    }

}

这种写法在多线程访问时会出现多线程问题:当第一个线程第一次运行到if判断和实例对象之间时,又一个线程抢走了cpu,判断对象为空,执行了实例化,而当第一个线程抢回cpu接着从if判断和实例对象之间向下执行时,就会出现实例化多次的问题

  • 第二种写法(单例模式: 懒汉式,线程安全,但性能较低
/**
 * 单例模式: 懒汉式,线程安全,但性能较低
 */
public class SingletonDemo03 {

    private SingletonDemo03() {
    }

    private static SingletonDemo03 singletonDemo03 = null;

    public static synchronized SingletonDemo03 getInstance(){
        if(singletonDemo03 == null) {
            singletonDemo03 = new SingletonDemo03();
        }
        return singletonDemo03;
    }

    public String hello(String name) {
        return "hello " + name;
    }

}

这一种写法就是在上一种写法的基础上,给获取单例对象的方法加上了同步锁。这样写虽然保证了线程的安全,但性能较低,即每个线程在执行到这个方法时都需要用一次同步锁,耗费资源较多,运行时间也需要得多

  • 第三种写法(单例模式: 懒汉式 存在问题
public class SingletonDemo03 {

    private SingletonDemo03() {
    }

    private static SingletonDemo03 singletonDemo03 = null;

    public static  SingletonDemo03 getInstance(){

        if(singletonDemo03 == null) {
            //当线程运行到这里时如果CPU被别的线程抢走就会引发多线程问题
            synchronized (SingletonDemo03.class) {
                singletonDemo03 = new SingletonDemo03();
            }
        }

        return singletonDemo03;
    }

}

这种写法依然会有线程安全问题(就是在代码中我备注的地方)

当第一个线程第一次运行到if判断和上synchronized锁之间时如果CPU被别的线程抢走,那么抢到cpu的线程就会从头执行这个方法,这时if的判断结果为true,如果cpu没有被抢走,就会继续向下执行并实例对象,而当第一个线程抢回cpu时,就会从上次运行的地方继续向下运行(if判断和上synchronized锁之间),又生成一个实例

  • 第四种写法(单例模式: 懒汉式,双重检查单例
/**
 * 单例模式: 懒汉式,双重检查单例
 */
public class SingletonDemo03 {

    private SingletonDemo03(){
    }

    private static SingletonDemo03 singletonDemo03 = null;

    public static  SingletonDemo03 getInstance(){
        //减小同步块,并使用双重检查来保证线程安装
        if(singletonDemo03 == null) {
            synchronized (SingletonDemo03.class) {
                //双重检查
                if(singletonDemo03 == null) {
                    singletonDemo03 = new SingletonDemo03();
                }
            }
        }
        return singletonDemo03;
    }

}

这种写法通过在上锁前后都进行非空判断,方式减少了synchronized 锁资源的消耗,确保了线程的安全

  • 第五种写法(单例模式: 懒加载, 线程安全
/**
 * 单例模式: 懒加载, 线程安全
 */
public class SingletonDemo04 {

    //阻止外部实例化
    private SingletonDemo04(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //使用静态内部类来实例一个SingletonDemo04对象
    private static class SingletonDemoHolder {
        private final static SingletonDemo04 instance = new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance() {
        return SingletonDemoHolder.instance;
    }

}

这种写法中具备静态内部类 ,即在一个类中套了一个具有static关键字的类SingletonDemoHolder 类,静态内部类在访问时会先加载外部类,而外部类访问时不会加载内部类

这种写法的好处是使用了静态内部类,静态内部类只会初始化一次,是单线程的,自然不会出现线程安全问题

  • 第六种写法(可以保证单例,且线程安全
public enum  SingletonDemo05 {

    INSTANCE;

    public String hello(String name) {
        return "hello " + name;
    }
	
}

这种写法需要对JVM有深刻的了解,因为这里用到了jvm中的enum类(枚举型),enum类中无构造函数,因为它把构造函数交给了jvm虚拟机去初始化,INSTANCE关键字的意思是允许enum类实例,而且反射机制对这种写法无效,enum类天生就是单例模式,方法默认static

工厂模式

概念

用于产生对象的方法或者,称之为工厂。 上面所讲到的单例模式也可以看作为一个特殊的工厂:即一个类中只生产一个实例

使用原因

用于生产指定系列的对象

我们可以通过工厂模式,来集中控制对象的创建过程,这样可以给设计带来更多的灵活性。

比如:spring的IOC容器就是工厂模式的经典实现。

使用场景

这里举一个经典的例子

以鸭子为例,鸭子的类型有很多,真的鸭子,橡皮鸭,电子玩具鸭等。

如下图,Duck是抽象的鸭子类,是所有类型鸭子的父类,每种类型的鸭子都需要继承父类并重写父类中的方法,这里体现了OOP中的多态

 那么,如何才能方便的创建出各种鸭子,并将创建过程控制起来,以便于以后的维护和扩展?

见如下代码

首先我们将上图的类进行编写:

1.抽象鸭子父类Duck

public abstract class Duck {

    abstract public void quack();

}

2、 RubberDuck鸭子子类,继承Duck抽象父类

public class RubberDuck extends Duck {

    @Override
    public void quack() {
        System.out.println("我是橡皮鸭,");
    }

}

2、 WildDuck鸭子子类,继承Duck抽象父类

public class WildDuck extends Duck {

    @Override
    public void quack() {
        System.out.println("我是真鸭子");
    }

}

2、 DonaldDuck鸭子子类,继承Duck抽象父类

public class DonaldDuck extends Duck {

    @Override
    public void quack() {
        System.out.println("我是唐老鸭");
    }

}

 3、编写控制鸭子类创建的工厂DuckFactory 

注:加了static的方法、变量可以使用类名直接调用

public class DuckFactory {

    //私有化构造方法
    private DuckFactory(){}

    //static 饿汉模式实例出一个类型为鸭子工厂类的对象
    private static DuckFactory duckFactory = new DuckFactory();
    
    //定义图中的几种子类的鸭子类,类型用数字区分,可以手动增加
    public static final int WILD_DUCK = 1;

    public static final int RUBBER_DUCK = 2;

    public static final int DONALD_DUCK = 3;

    public static final int PJ_DUCK = 4;
    
    //依据鸭子类型得到鸭子实例的方法
    public static Duck getInstance(int duckType) {
        switch (duckType) {
            case WILD_DUCK:
                //返回直接实例化好的鸭子子类
                return new WildDuck();
            case RUBBER_DUCK:
                return new RubberDuck();
            case DONALD_DUCK:
                return new DonaldDuck();
            case PJ_DUCK:
                return new PJDuck();
            default:
                return null;
        }
    }

}

4、测试

public class Main {

    public static void main(String[] args) {

        //DuckFactory可以生产鸭子对象
        Duck donaldDuck = DuckFactory.getInstance(DuckFactory.DONALD_DUCK);
        donaldDuck.quack();

        Duck wildDuck = DuckFactory.getInstance(DuckFactory.WILD_DUCK);
        wildDuck.quack();

        Duck pjDuck = DuckFactory.getInstance(DuckFactory.PJ_DUCK);
        pjDuck.quack();
    }

}

本期文章的内容就分享到这里了,下期文章将给大家介绍抽象工厂、责任链模式、观察者模式等等,感兴趣的可以持续关注【小阿飞_】哦(*╹▽╹*)


感 谢 阅 读

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值