设计模式1:创建型-单例模式

作者: e4e52c116681 | 来源:发表于2019-02-10 11:21 被阅读20次

    如果你没有一颗最求完善的心,得过且过,请远离设计模式。
    如果你不知道设计原则,请远离设计模式。
    如果你为了学习设计模式而学习设计模式,又不是太需要设计模式,请远离设计模式。


    单例模式:保证一个类仅有一个实例,并提供一个访问他的全局访问点

    单例意味着什么?----崇高与孤独
    ?--什么样的对象适合用单例?
    |--一个对象足以完成任务时,没有必要去创建多个对象
    |--一个对象非常消耗资源,需要限制对象的创建
    

    比如:世界,你不会让别人随便去new,如何不让外部无法创建本类对象,
    如何给外部提供唯一的对象,是单例模式的核心,要达到这两点很简单。


    一、单例的四种形式--形式上的一切都仅是开始而已

    1.终极孤独--饿汉

    即便无人问津,我也永远存在

    饿汉式.png
    public class World {
        private static World sWorld = new World();
        //[1]私有化构造
        private World() {
            initWorld();//初始化世界
            System.out.println("世界已创建");
        }
        private void initWorld() {
        }
        //[2]返回内部静态实例
        public static World getInstance() {
            return sWorld;
        }
    }
    

    2.有你相伴--懒汉双检锁

    虽然我们不是同类,但感谢有你相伴

    懒汉双检锁.png
    public class World {
        private static volatile World sWorld;
        //[1]私有化构造
        private World() {
            initWorld();//初始化世界
            System.out.println("世界已创建");
        }
        private void initWorld() {
        }
        //[2]返回内部静态实例
        public static World getInstance() {
            if (sWorld == null) {//判断非空后--执行
                synchronized (World.class) {//加锁,保证多线程下的单例
                    if (sWorld == null) {//非空,创建实例
                        sWorld = new World();
                    }
                }
            }
            return sWorld;
        }
    }
    

    3.有你相伴--静态内部类

    和上面的功能基本一致,所以我喜欢这个

    静态内部类.png
    public class World {
        //[1]私有化构造
        private World() {
            initWorld();//初始化世界
            System.out.println("世界已创建");
        }
        private void initWorld() {
        }
        //[3]返回内部静态实例
        public static World getInstance() {
            return WorldHolder.sWorld;
        }
        //[2]创建内部类创建实例
        private static class WorldHolder {
            private static final World sWorld = new World();
        }
    }
    

    4.至简--枚举
    枚举.png
    public enum World {
        INSTANCE;
        World() {
            initWorld();//初始化世界
            System.out.println("世界已创建");
        }
        private void initWorld() {
        }
    }
    

    二、单例下的序列化与反射

    单例的价值在于一个程序中只用一个该对象实例
    如果有恶意份子通过反射创建了另一个世界会怎么样?

    1.单例的测试(以静态内部类版为例)

    通过debug看出两次获取的都是同一个世界,这就是单一实例

    单例测试.png
    public class God {
        public static void main(String[] args) {
            World world1 = World.getInstance();
            World world2 = World.getInstance();
        }
    }
    

    2.通过反射创建实例

    可见world3的内存地址已经不一样了,说明出现了第二个世界,也就是单例的失效
    不过应该没有人吃饱了没事干用反射创建单例对象吧,天堂有路你不走...

    反射测试.png
    public class God {
        public static void main(String[] args) {
            World world1 = World.getInstance();
            World world2 = World.getInstance();
            //通过反射创建
            Class<World> worldClass = World.class;
            try {
                Constructor<World> constructor = worldClass.getDeclaredConstructor(null);
                constructor.setAccessible(true);
                World world3 = constructor.newInstance();
                System.out.println(world3==world2);//false
                System.out.println(world1==world2);//true
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    3.通过反序列化创建对象

    如果你的单例类有序列化的需求(如,单例对象本地存储,单例对象网络传输)
    反序列化形成的实例也并非原来的实例

    反序列化.png
    ---->[World]-------------
    public class World implements Serializable {
    
    ---->[God]-------------
    public class God {
        public static void main(String[] args) {
            World world1 = World.getInstance();
            World world2 = World.getInstance();
    
            //通过反射创建
            Class<World> worldClass = World.class;
            try {
                Constructor<World> constructor = worldClass.getDeclaredConstructor(null);
                constructor.setAccessible(true);
                World world3 = constructor.newInstance();
                System.out.println(world3 == world2);//false
                System.out.println(world1 == world2);//true
            } catch (Exception e) {
                e.printStackTrace();
            }
            //通过反序列化创建对象
            try {
                //序列化输出
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("world.obj"));
                oos.writeObject(world1);
                //反序列化创建对象
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("world.obj"));
                World world4 = (World) ois.readObject();
                ois.close();
                System.out.println(world1 == world4);//false
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    4.发序列化的解决方案

    通过反序列化时的钩子函数:readResolve来控制序列化对象实例

    反序列化的防治.png
    ---->[World]-------------
    //解决反序列化创建实例的问题,readResolve创建的对象会直接替换io流读取的对象
    private Object readResolve() throws ObjectStreamException {
        return getInstance();
    }
    

    三、结尾小述

    1.优缺点
    优点:
    设计者严格管控这个类,全局作用。简化使用者使用
    只创建一个实例,避免频繁创建销毁对象,节省内存资源,减少系统开销
    
    缺点:
    没有抽象层,不利于扩展
    职责过重,违背单一职责原则
    
    2.常见的单例
    java.util.Calendar 标准单例,通过Calendar.getInstance方法获取对象
    java.lang.System 完全单例,不提供外部构造方法,全部以静态方法提供服务
    android.view.LayoutInflater 标准单例 ,通过LayoutInflater.from(Context)方法获取对象
    

    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 附录
    V0.1--github 2018-2-10

    发布名:设计模式1:创建型-单例模式
    捷文链接:https://www.jianshu.com/p/6c1f3e355ba9

    2.更多关于我
    笔名 QQ 微信
    张风捷特烈 1981462002 zdl1994328

    我的github:https://github.com/toly1994328
    我的简书:https://www.jianshu.com/u/e4e52c116681
    我的掘金:https://juejin.im/user/5b42c0656fb9a04fe727eb37
    个人网站:http://www.toly1994.com

    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持

    icon_wx_200.png

    相关文章

      网友评论

        本文标题:设计模式1:创建型-单例模式

        本文链接:https://www.haomeiwen.com/subject/hbbteqtx.html