弄懂软件设计模式(一):单例模式和策略模式

这篇具有很好参考价值的文章主要介绍了弄懂软件设计模式(一):单例模式和策略模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

        软件设计模式和设计原则是十分重要的,所有的开发框架和组件几乎都使用到了,比如在这小节中的单例模式就在SpringBean中被使用。在这篇文章中荔枝将会仔细梳理有关单例模式和策略模式的相关知识点,其中比较重要的是掌握单例模式的常规写法。希望对有需要的小伙伴有帮助~~~


文章目录

前言

一、单例模式singleton

1.1 饿汉式

1.2 懒汉式

1.3 懒汉式+悲观锁

1.4 双重检查锁

1.5 静态内部类写法

1.6 枚举单例 

二、策略模型Strategy

总结


一、单例模式singleton

        单例模式确保仅创建一个实例且避免在同一个项目中创建多个实例。其实就是在一次类加载中,只会对当前的类对象创建一次实例,我们不能通过new方法来实例化对象,而是只能调用类对象提供的getInstance方法获取已实例化的对象。

1.1 饿汉式

饿汉式相对来说是使用的比较多的一种单例模式的写法,在类中静态定义一个私有变量并在类加载的时候实例化该类对象。通过在getInstance方法中返回INSTANCE实例对象。

package com.mashibing.dp.singleton;

public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();

    private Mgr01() {};

    public static Mgr01 getInstance() {
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }
}

饿汉式是立即加载的,除了预防反序列化的问题之外几乎没有缺点,而且它是线程安全的,操作简单。

1.2 懒汉式

懒汉式不会在加载类的时候就实例化对象,是懒加载的(按需加载),但是会出现线程安全的问题。 

弄懂软件设计模式(一):单例模式和策略模式,软件设计模式与设计原则,设计模式,Java

比如这里两个线程同时打到INSTANCE上,就可能会有同时new出实例对象的风险,因此线程不安全。下面的示例demo中可以看到一个lambda表达式描述的方法。Lambda表达式是对线程Runnable接口匿名内部类的一种简写,这是因为我们这里在Runnable中只写一种内部方法

package com.mashibing.dp.singleton;

public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03() {
    }
    /**
     * 懒汉式
     */
    public static Mgr03 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->
                System.out.println(Mgr03.getInstance().hashCode())
            ).start();
        }
    }
}

 这里的new Thread()原本写法是

    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Mgr03.getInstance().hashCode())
        }
    }).start();

1.3 懒汉式+悲观锁

懒汉式加锁其实比较简单,直接使用synchronized关键字修饰加上悲观锁就可以了,操作比较简单,也比较完好地解决了线程安全问题,但这却是以牺牲效率为前提的,同时也并非序列化安全和反射安全的。

package com.mashibing.dp.singleton;

public class Mgr04 {
    private static Mgr04 INSTANCE;

    private Mgr04() {
    }

    /**
     * 懒汉式+同步锁
     * @return
     */
    public static synchronized Mgr04 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr04.getInstance().hashCode());
            }).start();
        }
    }
}

1.4 双重检查锁

        前面我们为了解决懒汉式带来的线程安全问题加入了锁机制,但却带来了代码效率的下降。这里可以使用双重检查的机制来解决代码效率的问题,优化了代码性能,同时也保证了线程安全和懒加载的机制。但实现起来确实略显复杂,调试也比较困难。

package com.mashibing.dp.singleton;

public class Mgr06 {
    //这里需要加上volatile的原因是因为Java中在编译中指令重排比较频繁,如果不加volatile会出现问题,
    private static volatile Mgr06 INSTANCE; //JIT

    private Mgr06() {
    }
    /**
     *双重检查单例写法
     * @return
     */
    public static Mgr06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Mgr06.class) {
                if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}

这里需要注意的是在静态变量中INSTANCE需要加上volatile关键字修饰!

volatile关键子的作用

  • 确保INSTANCE变量的可见性,防止出现空指针异常的问题

        被volatile修饰的变量在线程访问时会被强制从主内存中读取变量的值而不从本地缓存中读取,保证共享变量的可见性和有序性,在对该变量进行修改后线程会强制将更新后的值刷回主内存,而不仅仅更新线程的本地缓存。出现空指针异常的问题可能是因为其它线程无立即获取修改后的未被volatile关键字修饰的变量值。

  • 防止指令重排

        指令重排是CPU为了提高程序执行效率而执行的操作,如果INSTANCE变量未被volatile修饰,那么可能无法保证线程安全。

1.5 静态内部类写法

         可以看到静态内部类的写法会在对象类中自定义一个私有的静态内部类,在其中实例化对象并赋值给一个静态常量。既实现了实例化对象的懒加载,同时也保证了线程安全。该类的缺点是对于传参的限制在某些场景下可能不太使用。

package com.mashibing.dp.singleton;

public class Mgr07 {

    private Mgr07() {
    }

    private static class Mgr07Holder {
        private final static Mgr07 INSTANCE = new Mgr07();
    }

    /**
     * 静态内部类的写法
     * @return
     */
    public static Mgr07 getInstance() {
        return Mgr07Holder.INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }


}

1.6 枚举单例 

枚举单例是最完美的单例模式,有效的解决了Java类的反序列化问题,实现了序列安全和反射安全。但枚举单例并不是懒加载的,也不能被继承。

package com.mashibing.dp.singleton;

/**
 * 不仅可以解决线程同步,还可以防止反序列化。
 */
public enum Mgr08 {

    INSTANCE;

    public void m() {}

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }

}

非枚举类的单例模式会出现反序列化的问题,这时因为我们可以利用Java的反射机制通过Java中的.class文件加载class对象

枚举单例不能够被反序列化的原因:枚举类没有构造方法。

这里有关双重检查锁参考了掘金大佬的文章,出处如下:

https://juejin.cn/post/7206529406612062268?searchId=20230905212839127143297911190A3F76#heading-16


二、策略模型Strategy

        策略模型比较简单,在日常开发中的使用也比较多,策略模型中一般封装的是实现一个方法的不同执行方式。策略模型将对象和行为分开,属于行为型模式。行为被分为了行为策略接口和实现行为的类。

main文件 

主程序调用比较类Sort,传入类对象和相应的比较器接口的实现即可。 

package com.mashibing.dp.strategy;

import java.util.Arrays;

/**
 * writing tests first!
 * extreme programming
 */
public class Main {
    public static void main(String[] args) {
        Cat[] a = {new Cat(3, 3), new Cat(5, 5), new Cat(1, 1)};
        Sorter<Cat> sorter = new Sorter<>();
//        Dog[] b = {new Dog(3), new Dog(5), new Dog(1)};
//        Sorter<Dog> sorter = new Sorter<>();
       
        /**
         * 策略模式的选择,通过类加载的方式实现功能,代码的拓展性更强
         */
        sorter.sort(a,new CatWeightComparator());
        System.out.println(Arrays.toString(a));
        sorter.sort(a,new CatHeightComparator());
        System.out.println(Arrays.toString(a));
    }
}

Sort类

自定义一个策略选择类,在其中调用已经被重写了的comparator接口中的compare方法实现对传入的比较类的策略模型的调用。 

package com.mashibing.dp.strategy;

public class Sorter<T> {

    public void sort(T[] arr, Comparator<T> comparator) {
        for(int i=0; i<arr.length - 1; i++) {
            int minPos = i;

            for(int j=i+1; j<arr.length; j++) {
                minPos = comparator.compare(arr[j],arr[minPos])==-1 ? j : minPos;
            }
            swap(arr, i, minPos);
        }
    }

}

策略实现类

策略接口需要实现Java.util中的Comparator接口并重写其中的compare方法实现对象类的策略逻辑封装。

package com.mashibing.dp.strategy;

public class CatHeightComparator implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        if(o1.height > o2.height) return -1;
        else if (o1.height < o2.height) return 1;
        else return 0;
    }
}

        其实最简单的策略模型的应用就是通过 if...else... 来判断执行策略,但是这种方式相比而言比较混乱,可拓展性不是很好,因此需要通过接口实现类和Java泛型来自定义一些策略以供选择。


总结

        上面的内容中荔枝主要梳理了单例模式和策略模式,这是二十三种软件设计模式中的两种,理解几种典型的单例模式的写法,接下来的文章中荔枝也会持续学习并整理输出,希望未来越来越好哈哈哈哈哈~~~

今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~文章来源地址https://www.toymoban.com/news/detail-704312.html

到了这里,关于弄懂软件设计模式(一):单例模式和策略模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 软件设计模式系列之六——单例模式

    单例模式(Singleton Pattern)是一种常见的创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这意味着无论何时何地,只要需要该类的实例,都会返回同一个实例,而不是创建多个相同的实例。单例模式通常用于管理全局状态、资

    2024年02月07日
    浏览(40)
  • 【软件架构设计】支持大规模系统的设计模式和原则

    今天,即使是小型初创公司也可能不得不处理数 TB 的数据或构建支持每分钟(甚至一秒钟!)数十万个事件的服务。所谓“规模”,通常是指系统应在短时间内处理的大量请求/数据/事件。 尝试以幼稚的方式实现需要处理大规模的服务,在最坏的情况下注定要失败,或者在最

    2024年02月13日
    浏览(39)
  • 设计模式之开闭原则:如何优雅地扩展软件系统

    在现代软件开发中,设计模式是解决常见问题的最佳实践。其中,开闭原则作为面向对象设计的六大基本原则之一,为软件系统的可维护性和扩展性提供了强大的支持。本文将深入探讨开闭原则的核心理念,以及如何在实际项目中运用这一原则,以便更好地设计软件系统。

    2024年01月18日
    浏览(42)
  • 软件设计模式与体系结构-设计模式-行为型软件设计模式-策略模式

    策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装在独立的类中,使它们可以相互替换。策略模式使得算法可以独立于使用它们的客户端而变化。 下面是一个使用策略模式的简单代码示例,以解释其工作原理: 在上述示例中,策略模

    2024年02月13日
    浏览(122)
  • 软件设计模式系列之二十三——策略模式

    策略模式(Strategy Pattern)是一种行为型设计模式,它允许在运行时动态选择算法的行为。这意味着你可以定义一系列算法,将它们封装成独立的策略对象,然后根据需要在不修改客户端代码的情况下切换这些算法。策略模式有助于解决问题领域中不同行为的变化和扩展,同时

    2024年02月08日
    浏览(40)
  • 【设计模式】设计原则-开闭原则

    定义 作用 1、方便测试;测试时只需要对扩展的代码进行测试。 2、提高代码的可复用性;粒度越小,被复用的可能性就越大。 3、提高软件的稳定性和延续性,易于扩展和维护。 实现方式 通过“抽象约束、封装变化”来实现开闭原则。通过接口或者抽象类为软件实体定义一

    2024年02月15日
    浏览(37)
  • 【设计模式】设计原则-里氏替换原则

    定义 任何基类可以出现的地方,子类一定可以出现。 通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。 换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。 针对的问题 主要作用就是规范继承时子类的一些书写规则。

    2024年02月14日
    浏览(49)
  • 前端设计模式和设计原则之设计原则

    1 开闭原则 该原则指出软件实体(类、模块、函数等)应该 对扩展开放,对修改关闭 。也就是说,在添加新功能时,应该通过扩展现有代码来实现,而不是直接修改已有的代码。这样可以确保现有代码的稳定性,并且减少对其他部分的影响。 在上述例子中,有一个原始功能

    2024年02月07日
    浏览(39)
  • 【Java 设计模式】设计原则之开放封闭原则

    在软件开发中,设计原则是创建灵活、可维护和可扩展软件的基础。 这些原则为我们提供了指导方针,帮助我们构建高质量、易理解的代码。 ✨单一职责原则(SRP) ✨开放/封闭原则(OCP) ✨里氏替换原则(LSP) ✨依赖倒置原则(DIP) ✨接口隔离原则(ISP) ✨合成/聚合复

    2024年02月02日
    浏览(49)
  • 【Java 设计模式】设计原则之里氏替换原则

    在软件开发中,设计原则是创建灵活、可维护和可扩展软件的基础。 这些原则为我们提供了指导方针,帮助我们构建高质量、易理解的代码。 ✨单一职责原则(SRP) ✨开放/封闭原则(OCP) ✨里氏替换原则(LSP) ✨依赖倒置原则(DIP) ✨接口隔离原则(ISP) ✨合成/聚合复

    2024年01月20日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包