[设计模式] 常见的设计模式

news2024/11/27 16:39:26

文章目录

  • 设计模式的 6 大设计原则
  • 设计模式的三大分类
  • 常见的设计模式有哪几种
    • 1. 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。(连接池)
      • 1. 饿汉式
      • 2. 懒汉式
      • 3. 双重检测
    • 2. 工厂模式
    • 3. 观察者模式
      • ● 推模型
      • ● 拉模型
    • 4. 装饰模式
    • 5. 建造者模式
    • 6. 代理模式
    • 7. 策略模式

设计模式的 6 大设计原则

  1. 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
  2. 开放封闭原则:软件实体可以扩展,但是不可修改。即面对需求,对程序的改动可以通过增加代码来完成,但是不能改动现有的代码。
  3. 里氏代换原则:一个软件实体如果使用的是一个基类,那么一定适用于其派生类。即在软件中,把基类替换成派生类,程序的行为没有变化。
  4. 依赖倒转原则:抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要针对实现编程。
  5. 迪米特原则:如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某个方法的话,可以通过第三个类转发这个调用。
  6. 接口隔离原则:每个接口中不存在派生类用不到却必须实现的方法,如果不然,就要将接口拆分,使用多个隔离的接口。

设计模式的三大分类

  1. 创造型模式:单例模式、工厂模式、建造者模式、原型模式 (4)
  2. 结构型模式:适配器模式、桥接模式、外观模式、组合模式、装饰模式、享元模式、代理模式 (7)
  3. 行为型模式:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式 (12)

注意:简单工厂模式 违背了六大原则中的开发-封闭原则,故而不属于23种GOF设计模式之一 也叫静态工厂方法模式

常见的设计模式有哪几种

1. 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。(连接池)

单例模式的实现需要三个必要的条件

  1. 单例类的构造函数必须是私有的,这样才能将类的创建权控制在类的内部,从而使得类的外部不能创建类的实例。
  2. 单例类通过一个私有的静态变量来存储其唯一实例。
  3. 单例类通过提供一个公开的静态方法,使得外部使用者可以访问类的唯一实例。

另外,实现单例类时,还需要考虑三个问题:

  1. 创建单例对象时,是否线程安全。
  2. 单例对象的创建,是否延时加载。
  3. 获取单例对象时,是否需要加锁(锁会导致低性能)。

下面介绍五种实现单例模式的方式。

1. 饿汉式

饿汉式的单例实现比较简单,其在类加载的时候,静态实例 instance 就已创建并初始化好了。

public class Singleton { 
  private static final Singleton instance = new Singleton();
  
  private Singleton () {}
  
  public static Singleton getInstance() {
    return instance;
  }
}

饿汉式单例优缺点:

  • 优点:
    • 单例对象的创建是线程安全的;
    • 获取单例对象时不需要加锁。
  • 缺点:单例对象的创建,不是延时加载。

一般认为延时加载可以节省内存资源。但是延时加载是不是真正的好,要看实际的应用场景,而不一定所有的应用场景都需要延时加载。

2. 懒汉式

与饿汉式对应的是懒汉式,懒汉式为了支持延时加载,将对象的创建延迟到了获取对象的时候,但为了线程安全,不得不为获取对象的操作加锁,这就导致了低性能。

public class Singleton { 
  private static final Singleton instance;
  
  private Singleton () {}
  
  public static synchronized Singleton getInstance() {    
    if (instance == null) {      
      instance = new Singleton();    
    }    

    return instance;  
  }
}

懒汉式单例优缺点:

  • 优点:
    • 对象的创建是线程安全的。
    • 支持延时加载。
  • 缺点:获取对象的操作被加上了锁,影响了并发度。
    • 如果单例对象需要频繁使用,那这个缺点就是无法接受的。
    • 如果单例对象不需要频繁使用,那这个缺点也无伤大雅。

3. 双重检测

饿汉式和懒汉式的单例都有缺点,双重检测的实现方式解决了这两者的缺点。
双重检测将懒汉式中的 synchronized 方法改成了 synchronized 代码块。

public class Singleton { 
  private static Singleton instance;
  
  private Singleton () {}
  
  public static Singleton getInstance() {
    if (instance == null) {
      synchronized(Singleton.class) { // 注意这里是类级别的锁
        if (instance == null) {       // 这里的检测避免多线程并发时多次创建对象
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

双重检测单例优点:

  • 对象的创建是线程安全的。
  • 支持延时加载。
  • 获取对象时不需要加锁。

2. 工厂模式

包括简单工厂模式、抽象工厂模式、工厂方法模式

  1. 简单工厂模式:主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
public class SimpleFactory {
    public static Product createProduct(String type) {
        if (type.equals("A")) {
            return new ProductA();
        } else if (type.equals("B")) {
            return new ProductB();
        } else {
            return null;
        }
    }
}

public interface Product {
    void use();
}

public class ProductA implements Product {
    @Override
    public void use() {
        System.out.println("Using Product A");
    }
}

public class ProductB implements Product {
    @Override
    public void use() {
        System.out.println("Using Product B");
    }
}

b. 工厂方法模式:修正了简单工厂模式中不遵守开放封闭原则。把选择判断移到了客户端去实现,如果想添加新功能就不用修改原来的类,直接修改客户端即可。

public interface Factory {
    Product createProduct();
}

public class ProductAFactory implements Factory {
    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

public class ProductBFactory implements Factory {
    @Override
    public Product createProduct() {
        return new ProductB();
    }
}

c. 抽象工厂模式:定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。

public interface AbstractFactory {
    Product createProductA();
    Product createProductB();
}

---

public class AbstractProductFactory implements AbstractFactory {
    @Override
    public Product createProductA() {
        return new ProductAFactory().createProduct();
    }

    @Override
    public Product createProductB() {
        return new ProductBFactory().createProduct();
    }
}
public class Client {
    public static void main(String[] args) {
        AbstractFactory factory = new AbstractProductFactory();
        Product productA = factory.createProductA();
        Product productB = factory.createProductB();
        productA.use();
        productB.use();
    }
}


3. 观察者模式

定义了一种一对多的关系,让多个观察对象同时监听一个主题对象,主题对象发生变化时,会通知所有的观察者,使他们能够更新自己。(微信朋友圈动态通知、消息通知、邮件通知、广播通知、桌面程序的事件响应)
在观察者模式中,又分为推模型和拉模型两种方式。

● 推模型

主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。

● 拉模型

主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
image.png

4. 装饰模式

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成派生类更为灵活。(输入输出流)

文件流 -> 输入输出流 -> 缓冲池流 (层层包装,扩展功能)

 BufferedReader in1 = new BufferedReader(new InputStreamReader(new FileInputStream(file)));//字符流
 DataInputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));//字节流
  // DataInputStream-从数据流读取字节,并将它们装换为正确的基本类型值或字符串
  // BufferedInputStream-可以通过减少读写次数来提高输入和输出的速度

5. 建造者模式

建造者模式的目的是为了分离对象的属性与创建过程。


建造者模式是构造方法的一种替代方案,为什么需要建造者模式,我们可以想,假设有一个对象里面有20个属性:

● 属性1
● 属性2
● ...
● 属性20

对开发者来说这不是疯了,也就是说我要去使用这个对象,我得去了解每个属性的含义,然后在构造函数或者Setter中一个一个去指定。更加复杂的场景是,这些属性之间是有关联的,比如属性1=A,那么属性2只能等于B/C/D,这样对于开发者来说更是增加了学习成本,开源产品这样的一个对象相信不会有太多开发者去使用。
为了解决以上的痛点,建造者模式应运而生,对象中属性多,但是通常重要的只有几个,因此建造者模式会让开发者指定一些比较重要的属性或者让开发者指定某几个对象类型,然后让建造者去实现复杂的构建对象的过程,这就是对象的属性与创建分离。这样对于开发者而言隐藏了复杂的对象构建细节,降低了学习成本,同时提升了代码的可复用性。

@Data
public class CarBuilder {
    // 车型
    private String type;
    
    // 动力
    private String power;
    
    public Car build() {
        Assert.assertNotNull(type);
        Assert.assertNotNull(power);
        return new Car(this);
    }


    public CarBuilder type(String type) {
        this.type = type;
        return this;
    }

    public CarBuilder power(String power) {
        this.power = power;
        return this;
    }

}
@Test
public void test() {
    Car car = new CarBuilder()
        .power("动力一般")
        .type("紧凑型车")
        .build();
        
    System.out.println(JSON.toJSONString(car));
}

6. 代理模式

  • 优点:代理可以协调调用方与被调用方,降低了系统的耦合度。根据代理类型和场景的不同,可以起到控制安全性、减小系统开销等作用。
  • 缺点:增加了一层代理处理,增加了系统的复杂度,同时可能会降低系统的相应速度。

Aop 就是使用代理模式来实现的。

public interface Subject {
    void request();
}

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject handles the request");
    }
}


---

public class Proxy implements Subject {
    private RealSubject realSubject;

    @Override
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        System.out.println("Proxy handles the request");
        // before aop
        realSubject.request();

        // post aop
    }
}

public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}


7. 策略模式

优缺点

  • 优点:策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为。干掉复杂难看的if-else。
  • 缺点:调用时,必须提前知道都有哪些策略模式类,才能自行决定当前场景该使用何种策略。
public interface Strategy {
    void execute();
}

public class ConcreteStrategyA implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy A");
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy B");
    }
}

public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.execute();
    }
}

public class Client {
    public static void main(String[] args) {
        Strategy strategyA = new ConcreteStrategyA();
        Strategy strategyB = new ConcreteStrategyB();

        Context context = new Context(strategyA);
        context.executeStrategy();

        context = new Context(strategyB);
        context.executeStrategy();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1257159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows上常用的dos命令

cd更改目录 cd c:\users从其他目录改成c:\users。 type显示文件内容 type good.txt显示文件good.txt里边的内容。 del删除文件 del good.txt删除文件。

(C++)string类的模拟实现

愿所有美好如期而遇 前言 我们模拟实现string类不是为了去实现他,而是为了了解他内部成员函数的一些运行原理和时间复杂度,在将来我们使用时能够合理地去使用他们。 为了避免我们模拟实现的string类与全局上的string类冲突(string类也在std命名空间中)&…

Redis序列化操作

目录 1.protostuff 的 Maven 依赖 2.定义实体类 3.序列化工具类 ProtostuffSerializer 提供了序列化和反序列化方法 4.测试 利用 Jedis 提供的字节数组参数方法,如: public String set(String key, String value) public String set(byte[] key…

【Springboot系列】SpringBoot整合WebSocket,既然如此简单(含源码)

文章目录 前言:什么是WebSocket?Spring Boot中的WebSocket支持WebSocket和HTTP优劣势WebSocket的优势:1.实时性:2.较低的延迟:3.较小的数据传输量:4.更好的兼容性: HTTP的优势:1.简单…

HDMI接口信号流向及原理图分析

1、HDMI的来源及发展 如今显示器上最常用的接口无非HDMI(High Definition Multimedia Interface)与DP(DisplayPort)两种,VGA与DVI已经很少使用,原因在于VGA传输的是模拟信号,在发送端需要将数字…

如果客户端同时有ipv4和ipv6,浏览器是如何选择用哪种ip

在互联网协议(IP)的发展历程中,IPv4和IPv6是两种主要的版本。对于一个客户端来说,同时拥有IPv4和IPv6的能力是常见的情况。那么,当一个客户端同时具有IPv4和IPv6的能力时,浏览器是如何选择使用哪种IP进行通…

数组栈的实现

1.栈的概念及结构 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底 栈中的数据元素遵守后进先出LIFO,(Last In First Out)的原则 压栈&…

Redis-Redis多级缓存架构

多级缓存架构 缓存设计 缓存穿透 缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失…

NX二次开发UF_CURVE_ask_offset_parms 函数介绍

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_offset_parms Defined in: uf_curve.h int UF_CURVE_ask_offset_parms(tag_t offset_curve_object, UF_CURVE_offset_data_p_t offset_data_pointer ) overview 概述 …

从0开始学习JavaScript--JavaScript中的对象原型

JavaScript中的对象原型是理解该语言核心概念的关键之一。本文将深入探讨JavaScript对象原型的作用、使用方法以及与继承相关的重要概念。通过详细的示例代码和全面的讲解,将能够更好地理解和运用JavaScript对象原型,提高代码的可维护性和扩展性。 Java…

windows下docker环境搭建与运行实战

背景 学习docker使用,需要环境,今天主要的目标是在windows环境下安装docker环境。 为什么要这么搞,主要是企业内部服务器,都是跟公网隔离的,没有访问公网权限,所以镜像什么的,从公网拉取完全没…

31 - MySQL调优之SQL语句:如何写出高性能SQL语句?

从今天开始,我将带你一起学习 MySQL 的性能调优。MySQL 数据库是互联网公司使用最为频繁的数据库之一,不仅仅因为它开源免费,MySQL 卓越的性能、稳定的服务以及活跃的社区都成就了它的核心竞争力。 我们知道,应用服务与数据库的交…

C语言:写一个函数,实现3*3矩阵的转置(指针)

分析: 在主函数 main 中,定义一个 3x3 的整型数组 a,并定义一个指向整型数组的指针 p。然后通过循环结构和 scanf 函数,从标准输入中读取用户输入的 3x3 矩阵的值,并存储到数组 a 中。 接下来,调用 mov…

MySQL的undo log 与MVCC

文章目录 概要一、undo日志1.undo日志的作用2.undo日志的格式3. 事务id(trx_id) 二、MVCC1.版本链2.ReadView3.REPEATABLE READ —— 在第一次读取数据时生成一个ReadView4.快照读与当前读 小结 概要 Undo Log:数据库事务开始之前&#xff0…

构建SQL Server链接服务器:实现跨服务器数据访问及整合

点击上方蓝字关注我 在SQL Server数据库管理中,链接服务器是一项强大的功能,允许在一个SQL Server实例中访问另一个SQL Server实例的数据。这种功能为数据库管理员提供了灵活性,使其能够跨不同服务器进行数据交互,开辟了更多的应用…

realname,soname和linkname

背景 当在看/lib下的一些文件时候,我们发现几乎都是三个动态库文件,为啥? 分析 当我发布一个动态库的时候,比如版本是maj.min.patch(10.2.1)的格式,当我改了小版本的号的时候(10…

牛客网刷题笔记四 链表节点k个一组翻转

NC50 链表中的节点每k个一组翻转 题目: 思路: 这种题目比较习惯现在草稿本涂涂画画链表处理过程。整体思路是赋值新的链表,用游离指针遍历原始链表进行翻转操作,当游离个数等于k时,就将翻转后的链表接到新的链表后&am…

【Flutter 常见问题系列 第 1 篇】Text组件 文字的对齐、数字和字母对齐中文

TextStyle中设置height参数即可 对齐的效果 Text的高度 是根据 height 乘于 fontSize 进行计算的、这里指定heiht即可、不指定的会出现 无法对齐的情况,如下: 这种就是无法对齐的情况

成为AI产品经理——模型评估(混淆矩阵)

一、混淆矩阵 1.混淆矩阵的介绍 混淆矩阵有两个定义positive(正例)和negative(反例)。分别代表模型结果的好和坏。 下图就是一个分类问题的混淆矩阵。横行代表真实的情况,而竖行代表预测的结果。 为了便于理解&…

原神「神铸赋形」活动祈愿现已开启

亲爱的旅行者,「神铸赋形」活动祈愿现已开启,「单手剑静水流涌之辉」「法器碧落之珑」概率UP! 活动期间,旅行者可以在「神铸赋形」活动祈愿中获得更多武器与角色,提升队伍的战斗力! 〓祈愿时间〓 4.2版本更…