设计模式

Posted weixin_42412601

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式相关的知识,希望对你有一定的参考价值。

目录

1、设计模式概述和分类

1.1、设计模式介绍

1)设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
2)设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度。
3)<<设计模式>> 是经典的书,作者是 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides Design(俗称 “四人组 GOF”)
4)设计模式并不局限于某种语言,java,php,c++ 都有设计模式.

1.2、设计模式类型

设计模式分为三种类型,共 23 种
创建型模式:解决的问题是对对象的创建怎么去设计代码

  • 单例模式、简单工厂模式、抽象工厂模式、原型模式、建造者模式。

结构型模式:解决的是,怎么让我们的软件更加的有伸缩性、扩展性。

  • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

行为型模式:主要是站在方法的角度,来思考设计代码

  • 模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

注意:不同的书籍上对分类和名称略有差别

2、单例模式

使用单例模式的场景:整个系统只需要拥有一个的对象的时候,就可以使用单例模式。

2.1、饿汉式

public class Hungry 
    //私有构造函数
    private Hungry()
    
    private final static Hungry HUNGRY=new Hungry();
    public static Hungry getInstance()
        return HUNGRY;
    

  • 类加载时就初始化,如果实例没用到,就可能出现浪费内存的情况;
  • 饿汉式属于立即加载,所以不存在在多线程中出现错误的情况。

2.2、懒汉式

方式一:
解决了饿汉式,浪费内存的问题。

//懒汉式
public class Lazy 
    private  static Lazy  lazy;
    private Lazy()
    
	public static Lazy getInstance()
	  if (lazy == null) 
           return lazy = new Lazy();
        
	    return lazy;
	

单线程好使,但是多线程下容易出事,一个线程执行到判断是否为空的语句,if(lazy ==null)的时候,当前线程被阻塞,而第二个线程进来了,这样的话第二个线程创建了新的对象,那么第一个线程被唤醒的时候又创建多一个对象,这样在内存中就存在了两个对象,明显和单例设计模式不符。
结论:在实际开发中,不推荐使用这种方式

方式二:

//懒汉式
public class Lazy 
    private  static Lazy  lazy;
    private Lazy()
    
	public static synchronized  Lazy getInstance()
	  if (lazy == null) 
           return lazy = new Lazy();
        
	    return lazy;
	

  • 解决了线程安全问题
  • 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return就行了。方法进行同步效率太低

结论:在实际开发中,不推荐使用这种方式

2.3、双重检测锁模式改进懒汉式 (DCL懒汉式)

//懒汉式
public class Lazy 
    private  static Lazy  lazy;
    private Lazy()
        System.out.println(Thread.currentThread().getName()+" ok");
    
    //双重检测锁模式,懒汉式 DCL
    public static Lazy getInstance()
        //第一重检测锁:只有为空,线程才抢锁,第一次并发时,所有线程进入。
        //以后的线程都不会进去抢锁了,提高效率
        if (lazy == null) 
            //第一次并发,只有一条线程进入,创建了对象
            synchronized (Lazy.class) 
                if (lazy == null) 
                //第二重检测锁,防止第一条线程创建了对象后,把锁放了,别的线程进来创建对象
                //只要有一条线程创建了对象,就过滤其他线程
                    return lazy = new Lazy();
                
            
        
        return lazy;
    
    public static void main(String[] args) 
        for (int i = 0; i < 10; i++) 
            new Thread(()->
                Lazy.getInstance();
            ).start();
        
    

结果:Thread-0 ok
  • Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton ==null)检查,这样就可以保证线程安全了。
  • 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步.
  • 线程安全;延迟加载;效率较高

结论:在实际开发中,推荐使用这种单例设计模式

2.4、静态内部类式

  • 使用静态内部类解决了线程安全问题,并实现了延时加载。
  • 静态内部类:不会在外部类初始化时就直接加载,只有当调用了getInstance方法时才会静态加载,类的装载是线程安全,final保证了在内存中只有一份。

推荐使用

//静态内部类实现
public class SingletonDemo4 
    private SingletonDemo4() 
    
    private static class InnerClass
        private static final SingletonDemo4 instance = new SingletonDemo4();
    
    public static SingletonDemo4 getInstance() 
        return InnerClass.instance;
    

//测试
class SingletonDemo4Test 
    public static void main(String[] args) 
        SingletonDemo4 instance = SingletonDemo4.getInstance();
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        System.out.println(instance == instance1); //输出true

        for (int i = 0; i < 10; i++) 
            new Thread(()->
                SingletonDemo4 instance2 = SingletonDemo4.getInstance();
                System.out.println(instance2);
            ).start();
        
    

//结果:
true
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c
com.example.learnthread.single.SingletonDemo4@2ceac56c

2.5、枚举方式

  • 严格意义上来说以上方式实现的单例模式都不是线程安全的,因为反射机制的存在,反射可以破坏私有属性,并且通过反射创建对象。
  • 反射遇到枚举时直接抛出异常,因此,枚举是创建单例的不二之选。

推荐使用

//如果框架里的单例是有问题的,很容易被人整
//enum本身也是一个Class类
public enum EnumSingle 
    //枚举项
    INSTANCE;

    public static EnumSingle getInstance()
        return INSTANCE;
    

//测试
class Test
    public static void main(String[] args) 
        EnumSingle instance1 = EnumSingle.INSTANCE;
        EnumSingle instance2 = EnumSingle.INSTANCE;
        System.out.println(instance1==instance2);

        for (int i = 0; i < 10; i++) 
            new Thread(()->
                EnumSingle instance = EnumSingle.getInstance();
                System.out.println(instance);
            ).start();
        
    

//结果:是线程安全的,但不是懒加载的
true
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE
INSTANCE

2.6、单例模式在 JDK的应用

1)JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
2)代码分析+Debug 源码+代码说明

2.7、单例模式注意事项和细节说明

1)单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
3)单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

2.8、实战例子

/**
 * @Description 枚举单例模式创建RestTemplate
 * @date 2021/5/28 10:58
 * @Author liuzhihui
 * @Version 1.0
 */
public enum RestTemplateEnum 
    //创建一个枚举对象,该对象天生为单例
    INSTANCE;

    private RestTemplate restTemplate ;

    RestTemplateEnum() 
        restTemplate = new RestTemplate();
    

    public RestTemplate getInstance()
        return restTemplate;
    


public class TestSingle 
    public static void main(String[] args) 
        System.out.println(RestTemplateEnum.INSTANCE.getInstance() == RestTemplateEnum.INSTANCE.getInstance());

        for (int i = 0; i < 10; i++) 
            new Thread(()->
                RestTemplate instance = RestTemplateEnum.INSTANCE.getInstance();
                System.out.println(instance);
            ).start();
        
//        结果:
//        true
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
//        org.springframework.web.client.RestTemplate@3af5b334
    

3、单例模式

3.1、简单工厂模式

  • 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
  • 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
  • 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.


创建一个接口:

public interface Shape 
   void draw();

创建实现接口的实体类。

public class Rectangle implements Shape 
   @Override
   public void draw() 
      System.out.println("Inside Rectangle::draw() method.");
   

public class Square implements Shape 
   @Override
   public void draw() 
      System.out.println("Inside Square::draw() method.");
   

public class Circle implements Shape 
   @Override
   public void draw() 
      System.out.println("Inside Circle::draw() method.");
   

创建一个工厂,生成基于给定信息的实体类的对象,如果以后新增了实体类,只需要在这里修改代码即可,不需要在使用方修改代码,遵循了开闭原则

public class ShapeFactory 
   //使用 getShape 方法获取形状类型的对象
   public Shape getShape(String shapeType)
      if(shapeType == null)
         return null;
              
      if(shapeType.equalsIgnoreCase("CIRCLE"))
         return new Circle();
       else if(shapeType.equalsIgnoreCase("RECTANGLE"))
         return new Rectangle();
       else if(shapeType.equalsIgnoreCase("SQUARE"))
         return new Square();
      
      return null;
   

使用方:

public class FactoryPatternDemo 
   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();
   

3.2、抽象工厂模式

  • 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
  • 将工厂抽象成两层,AbsFactory(抽象工厂) 和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
  • 简单说就是先生产出工厂,再使用工厂去生产对象

类图:

1、接口和实体类:

public interface Color 
    void fill();

public class Red implements Color 
    @Override
    public void fill() 
        System.out.println("fill red");
    

public class Blue implements Color 
    @Override
    public void fill() 
        System.out.println("fill blue");
    

public interface Shape 
    void draw();

public class Rectange implements Shape
    @Override
    public void draw() 
        System.out.println("draw Rectange");
    

public class Circle implements Shape 
    @Override
    public void draw() 
        System.out.println("draw Circle");
    

2、定义一个抽象工厂,用于生产实体对象:

public abstract class AbstractFactory 
    public abstract Color getColor(String color);
    public abstract Shape getShape(String shape);

3、定义具体的工厂:

public class ColorFactory extends AbstractFactory
    @Override
    public Color getColor(String color) 
        if (color.equalsIgnoreCase("red"))
            return new Red();
        else  if (color.equalsIgnoreCase("blue"))
            return new Blue();
        
        return null;
    
    @Override
    public Shape getShape(String shape) 
        return null;
    


public class ShapeFactory extends AbstractFactory
    @Override
    public Color getColor(String color) 
        return null;
    

    @Override
    public Shape getShape(String shape) 
        if (shape.equalsIgnoreCase("circle"))
            return new Circle();
        else if(shape.equalsIgnoreCase("rectange"))
            return new Rectange();
        
        return null;
    

实际上现在就是最初的简单工厂模式。

4、定义生产工厂的超级工厂

public class FactoryProduct 
    public static AbstractFactory getFactory(String factory)
        if (factory.equalsIgnoreCase("color"))
            return new ColorFactory();
        else if (factory.equalsIgnoreCase("shape"))
            return new ShapeFactory();
        
        return null;
    

5、使用方

public class Test 
    public static void main(String[] args) 
        //生产出工厂
        AbstractFactory factory = FactoryProduct.getFactory("color");
        //再使用工厂生产对象
        Color red = factory.getColor("red");
        red.fill();

        factory = FactoryProduct.getFactory("shape");
        Shape circle = factory.getShape("circle");
        circle.draw();
    

3.3、工厂模式在 JDK-Calendar 应用的源码分析

JDK 中的Calendar 类中,就使用了简单工厂模式:

public static void main(String[] args) 
	// TODO Auto-generated method stub
	// getInstance 是 Calendar 静态方法
	Calendar cal = Calendar.getInstance();
	// 注意月份下标从 0 开始,所以取月份要+1
	System.out.println("年:" + cal.get(Calendar.YEAR));
	System.out.println(" 月 :" + (cal.get(Calendar.MONTH) + 1)); System.out.println("日:" + cal.get(Calendar.DAY_OF_MONTH));
	System.out.println("时:" + cal.get(Calendar.HOUR_OF_DAY));
	System.out.println("分:" + cal.get(Calendar.MINUTE));
	System.out.println("秒:" + cal.get(Calendar.SECOND));

    public static Calendar getInstance()
    
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    

3.4、工厂模式的意义

  • 解耦:把对象的创建和使用的过程分开。就是Class A 想调用 Class B ,那么A只是调用B的方法,而至于B的实例化,就交给工厂类。
  • 工厂模式可以降低代码重复。如果创建对象B的过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。我们可以这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的创建过程的修改维护。
  • 由于创建过程都由工厂统一管理,所以发生业务逻辑变化,不需要找到所有需要创建B的地方去逐个修正,只需要在工厂里修改即可,降低维护成本。
  • 因为工厂管理了对象的创建逻辑,使用者并不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。

举个例子:
一个数据库工厂:可以返回一个数据库实例,可以是mysql,oracle等。
这个工厂就可以把数据库连接需要的用户名,地址,密码等封装好,直接返回对应的数据库对象就好。不需要调用者自己初始化,减少了写错密码等等这些错误。调用者只负责使用,不需要管怎么去创建、初始化对象。

工厂模式适用的一些场景(不仅限于以下场景):

  • 对象的创建过程/实例化准备工作很复杂,需要初始化很多参数、查询数据库等。
  • 类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。

4、原型模式

基本介绍
1)原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2)原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
3)工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()

实体类:

@AllArgsConstructor
@Getter
@Setter
@ToString
public class Sheep implements Cloneable
    private String name;
    private int age;
    private String color;
    @Override
    protected Object clone()
        Sheep sheep=null;
        try 
            sheep=(Sheep)super.clone();
         catch (CloneNotSupportedException e) 
            e.printStackTrace();
        
        return sheep;
    

使用者:

public class Client 
    public static void main(String[] args) 
        //原型模式克隆
        Sheep sheep = new Sheep("tom00", 1, "白色");
        //克隆
        Sheep cloneSheep = (Sheep)sheep.clone();
        System.out.println(sheep.toString());
        System.out.println(cloneSheep.toString());
        System.out.println(sheep==cloneSheep);
    

原型模式在 Spring 框架中源码分析

例如:

@Data
@AllArgsConstructor
@Component
@Scope("prototype")
public class Monster   
    private int id=10;
    private String name="牛魔王";
    private String skill ="芭蕉扇";
    public Monster() 
        System.out.println("Monster 创建: ");
    

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void contextLoads() 
        Monster monster1 = (Monster)applicationContext.getBean("monster");
        Monster monster2 = (Monster)applicationContext.getBean("monster");
        System.out.println(monster1);
        System.out.println(monster2);
        System.out.println(monster1==monster2);
    

调试getBean方法。
发现如果是单例bean对象,第一次会去创建对象,然后把对象存到缓存中。
如果是原型对象,每次调用getBean都会去创建一个对象,根本不走缓存。
具体查看:https://blog.csdn.net/weixin_42412601/article/details/113094776

浅拷贝

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
  • 前面我们克隆羊就是浅拷贝
  • 浅拷贝是使用默认的 clone()方法来实现
@Getter
@Setter
@ToString
public class Sheep implements Cloneable
    private String name;
    private int age;
    private String color;
    //是一个对象
    private Sheep friend;
    public Sheep(String name, int age, String color) 
        this.name = name;
        this.age = age;
        this.color = color;
    
    @Override
    protected Object clone()
        Sheep sheep=null;
        try 
            sheep=(Sheep)super.clone();
         catch (CloneNotSupportedException e) 
            e.printStackTrace();
        
        return sheep;
    

    public static void main(String[] args) 
        //原型模式克隆
        Sheep sheep = new Sheep("tom00", 1, "白色");
        sheep.setFriend(new Sheep("jack",1,"红色"));
        //克隆
        Sheep cloneSheep = (Sheep)sheep.clone();

        System.out.println("原型:"+sheep.toString());
        System.out.println("克隆:"+cloneSheep.toString());
        System.out.println(sheep==cloneSheep)

以上是关于设计模式的主要内容,如果未能解决你的问题,请参考以下文章

当轮播中的图像发生变化时,导航栏中图标的颜色如何变化

Flutter 深色(暗黑)模式下 状态栏字体颜色为白色

添加 UISearchController 时导航栏变为白色

当计算从睡眠模式或待机模式唤醒时,基于图像的 jframe 变为白色

2D 白色网格不显示在背景上方

重新绘制图像时出现白色闪烁[颤动]