• 常见的五种设计模式


    https://www.runoob.com/design-pattern/factory-pattern.html

    单例模式

    **意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    **主要解决:**一个全局使用的类频繁地创建与销毁。
    **何时使用:**当您想控制实例数目,节省系统资源的时候。
    **如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
    **关键代码:**构造函数是私有的。
    应用实例:
    1、一个班级只有一个班主任。
    2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
    使用场景:

    • 1、要求生产唯一序列号。
    • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
    • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等

    优点:
    1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    2、避免对资源的多重占用(比如写文件操作)。
    缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
    注意:
    1、单例类只能有一个实例。
    2、单例类必须自己创建自己的唯一实例。
    3、单例类必须给所有其他对象提供这一实例。

    实现

    饿汉模式

    类加载的同时, 创建实例.

    public class SingletonHungry {
        //static 修饰成员变量,全局只有一个
        private static SingletonHungry instance = new SingletonHungry();
        //构造方法私有化,使类对象只有一个
         private SingletonHungry() {}
    //    对外提供一个获取获取实例对象的方法
    //    用static修饰方法
        public static SingletonHungry getInstance(){
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    懒汉模式-单线程版

    类加载的时候不创建实例. 第一次使用的时候才创建实例.

    public class SingletonLazy {
        private static SingletonLazy instance = null;
        private SingletonLazy() {}
        public static SingletonLazy getInstance() {
            if (instance == null) {
                instance = new SingletonLazy();
             }
        return instance;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    懒汉模式-多线程版

    上面的懒汉模式的实现是线程不安全的.
    线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致 创建出多个实例.
    一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改 instance 了)
    加上 synchronized 可以改善这里的线程安全问题.

    public class SingletonLazy2 {
        private static SingletonLazy2 instance = null;
        private SingletonLazy2() {}
    
    //    以下两种方法都可以
    //    在获取成员变量时,先判断锁是否被占用
    //
    //    其实synchronized代码块只需要执行一次就够了,以现在的写法,只要调用了getInstance方法,都要竞争锁,锁竞争是非常耗费系统资源的
    //    使用了synchronized就从用户态转到了内核态
        public static synchronized SingletonLazy2 getInstance() {
            if (instance == null) {
    //            初始化过程只执行一次
                    instance = new SingletonLazy2();
            }
            return instance;
        }
    
        public static  SingletonLazy2 getInstance1() {
            synchronized(SingletonLazy2.class) {
                if (instance == null) {
                    instance = new SingletonLazy2();
                }
                return instance;
            }
        }
    
    //    错误的!!!!!!!!!!!!
    //    public static SingletonLazy2 getInstance() {
    //        if (instance == null) {
    //    此时已经判断instance为空,争抢锁之后就会创建一个新的实例对象
    //            synchronized (SingletonLazy2.class){
    //                instance = new SingletonLazy2();
    //            }
    //        }
    //        return instance;
    //    }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    懒汉模式-多线程版(改进)

    以下代码在加锁的基础上, 做出了进一步改动:

    • 使用双重 if 判定, 降低锁竞争的频率.
    • 给 instance 加上了 volatile.
    /**
     * 使用双重 if 判定, 降低锁竞争的频率.
     * 给 instance 加上了 volatile.
     *
     * 加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.
     * 因此后续使用的时候, 不必再进行加锁了.
     * 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
     * 同时为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile .
     * 当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,
     * 其中竞争成功的线程, 再完成创建实例的操作.
     * 当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
     */
    //双重检查锁 DCL
    public class SingletonDCL {
    
        //synchronized只能保证原子性和可见性,不能保证有序性(其他线程可能得到一个创建了对象(instance != null),但没有得到某些数据初始化的对象)
        //加上volatile保证有序性(可见性与有序性)
        private volatile static SingletonDCL instance = null;
        private SingletonDCL() {}
        public static  SingletonDCL getInstance() {
            //为了让后面的线程不再获取锁,避免锁竞争
            if (instance == null) {
                synchronized (SingletonDCL.class) {
                    //完成初始化操作,只执行一次
                    if (instance == null) {
                        instance = new SingletonDCL();
                    }
                }
            }
            return instance;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    关于单例模式的饿汉和懒汉模式

    1. 工作中可以使用饿汉模式,因为书写简单且不易出现错
    2. 饿汉模式在程序加载时完成的初始化,但是由于计算机资源有限,为了节约资源,可以使用懒汉模式
    3. 懒汉模式就是在使用对象时再去完成初始化操作
    4. 懒汉模式在多线程模式可能出现线程安全问题
    5. 那么就需要使用synchronized包裹初始化代码块
    6. 初始化代码只执行一次,后序的线程在调用getInstance()时,依然会产生竞争锁,频繁进行用户态和内核态的切换,非常浪费所资源
    7. 这时候就是可以用double check lock(DCL)的方式,在外层加一个非空校验,避免无用的锁竞争
    8. synchronized只能保证原子性和可见性,不能保证有序性(其他线程可能得到一个创建了对象(instance != null),但没有得到某些数据初始化的对象),再使用volatile解决有序性问题
    9. 描述指令重排序可能出现的问题

    工厂模式

    **意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
    **主要解决:**主要解决接口选择的问题。
    **何时使用:**我们明确地计划不同条件下创建不同实例时。
    **如何解决:**让其子类实现工厂接口,返回的也是一个抽象的产品。
    **关键代码:**创建过程在其子类执行。
    应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
    优点:
    1、一个调用者想创建一个对象,只要知道其名称就可以了。
    2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
    3、屏蔽产品的具体实现,调用者只关心产品的接口。
    **缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
    使用场景:
    1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
    2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
    3、设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
    **注意事项:**作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
    工厂模式包含以下几个核心角色:

    • 抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
    • 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
    • 抽象工厂(Abstract Factory):声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
    • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。

    实现

    public static void main(String[] args) {
          ShapeFactory shapeFactory = new ShapeFactory();
     
          //获取 Circle 的对象,并调用它的 draw 方法
          Shape shape1 = shapeFactory.getShape("CIRCLE");
     
          //调用 Circle 的 draw 方法
          shape1.draw();
     
          //获取 Rectangle 的对象,并调用它的 draw 方法
          Shape shape2 = shapeFactory.getShape("RECTANGLE");
     
          //调用 Rectangle 的 draw 方法
          shape2.draw();
     
          //获取 Square 的对象,并调用它的 draw 方法
          Shape shape3 = shapeFactory.getShape("SQUARE");
     
          //调用 Square 的 draw 方法
          shape3.draw();
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    模板模式

    在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

    意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

    主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
    何时使用:有一些通用的方法。
    如何解决:将这些通用算法抽象出来。
    关键代码:在抽象类实现,其他步骤在子类实现。
    应用实例:
    1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
    2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。
    3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

    优点:
    1、封装不变部分,扩展可变部分。
    2、提取公共代码,便于维护。
    3、行为由父类控制,子类实现。

    缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

    使用场景:
    1、有多个子类共有的方法,且逻辑相同。
    2、重要的、复杂的方法,可以考虑作为模板方法。

    注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

    实现

    我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。
    TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

    public abstract class Game {
       abstract void initialize();
       abstract void startPlay();
       abstract void endPlay();
     
       //模板
       public final void play(){
          //初始化游戏
          initialize();
          //开始游戏
          startPlay();
          //结束游戏
          endPlay();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    public class Cricket extends Game {
     
       @Override
       void endPlay() {
          System.out.println("Cricket Game Finished!");
       }
     
       @Override
       void initialize() {
          System.out.println("Cricket Game Initialized! Start playing.");
       }
     
       @Override
       void startPlay() {
          System.out.println("Cricket Game Started. Enjoy the game!");
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class Football extends Game {
     
       @Override
       void endPlay() {
          System.out.println("Football Game Finished!");
       }
     
       @Override
       void initialize() {
          System.out.println("Football Game Initialized! Start playing.");
       }
     
       @Override
       void startPlay() {
          System.out.println("Football Game Started. Enjoy the game!");
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class TemplatePatternDemo {
       public static void main(String[] args) {
     //直接调用抽象类中的模板方法,模板方法中执行子类实现的方法
          Game game = new Cricket();
          game.play();
          System.out.println();
          game = new Football();
          game.play();      
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    策略模式

    意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
    主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
    何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
    如何解决:将这些算法封装成一个一个的类,任意地替换。
    关键代码:实现同一个接口。
    应用实例:
    1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
    2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
    3、JAVA AWT 中的 LayoutManager。

    优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
    缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

    使用场景:
    1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
    2、一个系统需要动态地在几种算法中选择一种。
    3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
    策略模式包含以下几个核心角色:

    • 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
    • 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
    • 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。

    策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。

    实现

    我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
    StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

    public interface Strategy {
       public int doOperation(int num1, int num2);
    }
    
    • 1
    • 2
    • 3
    public class OperationAdd implements Strategy{
       @Override
       public int doOperation(int num1, int num2) {
          return num1 + num2;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class OperationSubtract implements Strategy{
       @Override
       public int doOperation(int num1, int num2) {
          return num1 - num2;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class OperationMultiply implements Strategy{
       @Override
       public int doOperation(int num1, int num2) {
          return num1 * num2;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class Context {
       private Strategy strategy;
     
       public Context(Strategy strategy){
          this.strategy = strategy;
       }
     
       public int executeStrategy(int num1, int num2){
          return strategy.doOperation(num1, num2);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    public class StrategyPatternDemo {
       public static void main(String[] args) {
          Context context = new Context(new OperationAdd());    
          System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
     
          context = new Context(new OperationSubtract());      
          System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
     
          context = new Context(new OperationMultiply());    
          System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    代理模式

    意图:为其他对象提供一种代理以控制对这个对象的访问。
    主要解决:在直接访问对象时带来的问题。
    何时使用:想在访问一个类时做一些控制。
    如何解决:增加中间层。
    关键代码:实现与被代理类组合。
    应用实例:
    1、Windows 里面的快捷方式。
    2、买火车票不一定在火车站买,也可以去代售点。
    3、spring aop。

    优点: 1、职责清晰。 2、高扩展性。 3、智能化。

    缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

    使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

    注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

    实现

    public interface Image {
       void display();
    }
    
    • 1
    • 2
    • 3
    public class RealImage implements Image {
     
       private String fileName;
     
       public RealImage(String fileName){
          this.fileName = fileName;
          loadFromDisk(fileName);
       }
     
       @Override
       public void display() {
          System.out.println("Displaying " + fileName);
       }
     
       private void loadFromDisk(String fileName){
          System.out.println("Loading " + fileName);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    public class ProxyImage implements Image{
     
       private RealImage realImage;
       private String fileName;
     
       public ProxyImage(String fileName){
          this.fileName = fileName;
       }
     
       @Override
       public void display() {
          if(realImage == null){
             realImage = new RealImage(fileName);
          }
          realImage.display();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class ProxyPatternDemo {
       
       public static void main(String[] args) {
          Image image = new ProxyImage("test_10mb.jpg");
     
          // 图像将从磁盘加载
          image.display(); 
          System.out.println("");
          // 图像不需要从磁盘加载
          image.display();  
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对比

    工厂模式:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行

    ShapeFactory shapeFactory = new ShapeFactory();
    //获取 Circle 的对象,并调用它的 draw 方法
    Shape shape1 = shapeFactory.getShape("CIRCLE");
    
    • 1
    • 2
    • 3

    模板模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

    Game game = new Cricket();
     game = new Football();
     game.play();
    
    • 1
    • 2
    • 3

    策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换

     Context context = new Context(new OperationAdd());
    
    • 1

    在模板模式中,我们在父类规定处理的流程,在子类实现具体处理。 如果我们将该模式用于生成实例,它就演变为工厂模式。

    一个“策略”是一个 整体的(完整的) 算法,算法是可以被整体替换的。而模板方法只能被替换其中的特定点,算法流程是固定不可变的

  • 相关阅读:
    peft模型微调--Prompt Tuning
    解决mac 下 docker-compose 不是命令
    天池-Python训练营练习01
    SQL错题集(二)
    Gateway + Oauth2实现单点登录
    静态博客搭建工具汇总
    Linux 命令(180)—— renice 命令
    maven打包出来的jar包引入的其他jar包版本不是pom文件配置的版本
    看完这篇 教你玩转渗透测试靶机Vulnhub——Hacksudo: Aliens
    Android 的整体架构
  • 原文地址:https://blog.csdn.net/qq_53869058/article/details/132789551