JAVA常用的七种设计模式

2023-11-16

学习设计模式之前,我们先要了解一下设计模式的怎么来的?

对于设计人员,特别是开发人员吗,往往受限于眼界或经验不能够体会到设计原则的实用性,或者在处理具体问题时,不知道如何把设计原则应用到到设计和代码,因此产生了“模式”。
随着参与的项目越来越多,人们发现:很多问题并不是一个项目中出现的,它会在很多的项目中出现。于是人们就把这些问题总结出来,然后给出了解决这些问题的方案,而这些方案–“模式”(解决问题的套路)。

设计模式的分类

1.创建模式:创建一些特殊的对象,或者在特殊要求下创建对象。
2.结构模式:主要利用组合/聚合或者继承,让类与类能够形成某种关联关系 – 代理。
3.行为模式:刻画了类和对象交换及分配职责的方式。

接下来我们正式介绍七种常用的设计模式

单例模式

1.饿汉模式(最基本的单例模式)

类加载时,会直接实例化单例对象,以后都返回该对象的引用。

  • 缺点:类加载时,会直接实例化单例对象,不管是否使用到该单例对象,浪费内存。
  • 优点:没有枷锁,执行效率高,线程安全的实例。
	public class Singleton{
		private Singleton{
		}
		//创建本类的私有构造方法
		private static Singleton singleton = new Singleton();
		
		public static Singleton getInstance(){
			return singleton;
		}
	}

2.懒汉模式(线程不安全、线程安全但效率低)

不要直接在类加载时实例化,而是在调用方法时,再实例化。

  • 优点:不会占用内存
  • 缺点:安全方面 单线程情况下,是安全的,但是在多线程下,多个线程可能同时执行singleton == null 都为true,会创建多个实例在内存中。
	public class LazySingleton{
		private LazySingleton(){
		}
		
		private static LazySingleton singleton;
		
		public static LazySingleton getInstance(){
			if(singleton == null){
				singleton = new LazySingleton();
			}
			return singleton;
		}
	}

2.1懒汉模式(双重检验模式(线程安全,且效率高的) 把锁的粒度变小,只锁第一次初始化时)

  • 实例会在调用getInstance方法时创建,仅在第一调用初始化时需要锁住。
		public class LazySingleton{
		private LazySingleton(){
		}
		
		private static LazySingleton singleton;
		
		public static LazySingleton getInstance(){
		/*
            双重检验
            首先先判断实例是否为null,为null则使用synchronized锁住类,
            然后在同步块中,再一次判断实例是否为null,为null则初始化实例。
            synchronized(需要锁的对象){}
        */
			if(singleton == null){
				synchronized(LazySingleton .class){
					if(singleton == null){
						singleton = new LazySingleton();
					}
				}
			}
			return singleton;
		}
	}

3.内部类实现模式

通过静态内部类,完成单例模式的创建。

  • 在外部类加载时,并不会加载内部类,也就是不会执行new 实例(),这属于懒加载。
  • 只有第一次调用getInstance方法时,才会加载。
	public class InnerSingleton{
		private InnerSingleton(){
		}
		
		private static class Inner{
			private static InnerSingleton instance = new InnerSingleton();
		}
		
		public static InnerSingleton getInstance(){
			return Inner.instance;
		}
	}

4.枚举实现

通过枚举创建 单例模式。

  • 实现单例的最佳方法。简洁,支持自动序列化机制,防止多次实例化,但目前还没有被广泛采用。
	public class EnumSingleton{
		private EnumSingleton(){
			
		}
		
		private static enum SinEnum{
			//自定义的枚举值,如果没有该自定义枚举值,无法获取枚举对象
			SIN;
			private EnumSingleton es = new EnumSingleton();
		}
		
		public static EnumSingleton getInstance(){
			SinEnum s = SinEnum.SIN;
			return s.es;
		}
	}

工厂模式

讲使用者和对象的生产者进行分离。

在工厂模式中,几乎都有三种角色,工厂(抽象工厂、具体工厂) 产品(抽象产品、具体产品) 使用者。使用者想要使用产品,不用自己去生产产品,把生产的动作交给工厂去做,使用者只需要从工厂提供产品的位置(方法)去拿就好。

  • 1.简单工厂模式–顾客需要给出清单。
    变化点在产品对象上,所以我们会抽象产品,然后通过一个工厂,根据不同的情况产生不同的产品对象。

  • 2.工厂方法模式–根据工厂能产生什么顾客拿什么。
    工厂可以产生统一品牌的商品,会根据商品去抽象工厂,对每一个产品,提供一个工厂实现类。

  • 3.抽象工厂模式–根据工厂能产生什么顾客拿什么,但是工厂能产生的产品会有多种品牌。
    超级工厂,可以生产不同品牌的各种产品,抽象出超级工厂,也要抽象出产品,然后根据不同的品牌给出该品牌商品的工工厂实现类。

原型模式

根据一个已经存在的对象,创建一个和他一样的对象。-- 克隆

浅克隆-- 利用Object中clone()实现

1.让被克隆的类实现Cloneable接口。

2.重写clone方法,方法访问修饰符public。

3.对象.clone()的方式的到一个一样的对象。

浅克隆指,被克隆的对象会产生一个新的,但是对象属性不会产生。

	public class Man implements Cloneable {
		private String name;
		public Car car = new Car();
		
		public Man clone() throws CloneNotSupportedException{
			Object obj = super.clone();
			return (Man)obj;
		}
	}

深度克隆

1.克隆对象所涉及的自定义类,需要实现序列化接口。

2.在需要克隆的类中,添加一个方法,完成序列化反序列化即可。

	public class Man implements Cloneable,Serializable {
		private String name;
		public Car car = new Car();
			
		public Man depthClone() throws IOException, ClassNotFoundException {
			//获取对象信息,把当前对象写入另一块内存
	        ByteArrayOutputStream bo = new ByteArrayOutputStream();
	        ObjectOutputStream objOut = new ObjectOutputStream(bo);
	        objOut.writeObject(this);
	
	        //读取内存 创建对象
	        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
	        ObjectInputStream objIn = new ObjectInputStream(bi);
	        Object obj = objIn.readObject();
	        return (Man) obj;
			}
		}

代理模式

1.静态代理

根据目标对象需要代理的行为,抽象出一个接口(包含了需要代理的行为),目标类和代理类都需要去实现该接口,然后将目标对象注入到代理类中,此时就可以在代理类中调用目标对象的行为,并为止附加非功能性逻辑。

2.动态代理之JDK代理

  • 第一步,实现接口InvocationHandler,然后重写invoke方法,在invoke方法中调用目标对的方法。

  • 第二步,提供一个自定义的方法,通过Proxy.newProxyInstance()得到代理对象。

	public class LivePeople implements InvocationHandler {

    private IEat target;//目标对象

    public LivePeople(IEat target) {
        this.target = target;
    }

    //获取代理对象
    public Object getProxy() {
        //Proxy()类中的newProxyInstance()方法
        /*
            第一个参数:目标对象的类加载器
            第二个参数:目标对象实现的所有接口
            第三个参数:代理类的对象
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("订餐");
        Object returnInfo = method.invoke(target, args);//执行目标对象的方法
        System.out.println("结账,开发票");
        return returnInfo;
    }
}

3.动态代理之Cglib代理

  • 第一步,导入Cglib依赖(包)。

  • 第二步,实现接口MethodInterceptor,重写intercept方法,在其中完成目标对象的方法调用。

  • 第三步,提供自定义方法,通过工具类获取得到代理对象。

	public class CglibProxy implements MethodInterceptor {

    private IEat target;

    public CglibProxy(IEat target) {
        this.target = target;
    }

    //给目标对象创建代理对象(自定义)
    public Object getProxyInstance() {
        //创建工具栏对象
        Enhancer en = new Enhancer();
        //设置目标对象父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建代理对象
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("订餐");
        Object returnInfo = methodProxy.invokeSuper(o, objects);//调用目标对象的方法并传入参数
        System.out.println("付款,开发票");
        return returnInfo;
    }
}

装饰器模式

对象功能的扩展能够根据需要来动态地实现。

以咖啡举例:

  • 1.根据对象抽象一个公共的接口。

  • 2.根据接口给出不同的实现类(主料类(主料类产生的对象就是被装饰的对象) 和 配料类–装饰类)。

  • 3.在配料类中注入被装饰的对象。

  • 4.生产咖啡时,先生产主料(被修饰的对象),然后用配料不断去修饰主料。

适配器模式

使得原本不兼容的两个接口(功能)可以兼容 – 搭建了两个接口间的桥梁。

	class Tel{
    public void call(){}
	}
	class Carame{
	    public void make(){}
	}
	class Phone extends Tel{
	    private Carame c = new Carame();
	    public void make(){
	        c.make();
	    }
	}

实现适配器的方案,继承或者依赖(推荐使用)

优点:

  • 可以让没有任何关联的类,一起运行;

  • 提高了类的复用

  • 灵活性好

缺点:

  • 过多的使用适配器,会导致系统非常混乱,不容具体把控

  • java是单继承。

观察者模式

  • 主题类(由它产生的对象 就是 被观察的对象) 继承 Observable的类。
    在主题类,需要针对主题(价格的变化进行关注,设置变化点,然后通知观察者价格有了变化)。
	public class Product extends Observable {
    private double price;
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
        // 通知观察者注意到主题的变化
        this.setChanged();// 设置变化点
        this.notifyObservers(price);//通知观察者
    }
}
  • 观察者类(由它产生的对象 就是 观察者) 实现Observer接口。
    重写update(当主题发生变化时,会调用方法)。
	public class ProductProxy1 implements Observer {
    private double price;
    /**
     * 当主题类的值发生变化后,会调用该方法
     * @param o 主题对象
     * @param arg 主题更新的值对象
     */
    @Override
    public void update(Observable o, Object arg) {
        double factoryPrice = (double) arg;
        this.price = factoryPrice * 1.5;
    }
    public double getPrice() {
        return price;
    }
}
  • 首先产生主题对象(被观察对象),产生观察者对象,然后给主题对象设置观察者,最后通过更改主题的值,测试观察者是否有观测到主题值的改变。
	public class Test {
    public static void main(String[] args) {
        // 产生主题对象  -- 被观察者
        Product product = new Product();
        // 产生观察者
        ProductProxy1 p1 = new ProductProxy1();
        // 给主题对象添加观察者
        product.addObserver(p1);
        // 主题发生变化
        product.setPrice(1000);
        System.out.println("出厂价格:"+ product.getPrice() +
                           "\n代理商售卖价格:" + p1.getPrice());
        // 主题发生变化
        product.setPrice(2000);
        System.out.println("出厂价格:"+ product.getPrice() + 
                           "\n代理商售卖价格:" + p1.getPrice());
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA常用的七种设计模式 的相关文章

  • Logback 上下文选择器的实际使用

    Logback 的文档测井分离 http logback qos ch manual loggingSeparation html表明我可以使用上下文选择器 http logback qos ch manual contextSelecto
  • Java 1.4 上的 ActiveMQ 客户端

    我们在最新版本 5 6 0 中使用 Active MQ 现在我们遇到一个问题 必须连接一个新客户端 不幸的是这个客户端是用IBM JDK 1 4开发的 将 ActiveMQ 添加到应用程序会导致以下错误 UNEXPECTED ERROR O
  • 添加@Named时@SessionScoped不起作用

    考虑以下支持 bean import javax faces bean ManagedBean import javax faces bean SessionScoped ManagedBean SessionScoped public c
  • setSize() 不起作用?

    我有一个程序 需要两个按钮 一个是常规按钮 另一个具有根据鼠标悬停而变化的图片 目前 由于图片很大 JButton自定义也很大 我可以更改自定义的大小并保持图像 和翻转图像 成比例吗 我尝试过 setSize 但它没有任何作用 对于任何反馈
  • Eclipse 调试“未找到源”

    我刚刚开始使用 Eclipse 所以慢慢来吧 但是 当尝试调试 JUnit 测试用例时 我会收到一个对话框 指出当我在测试方法中的代码中找到此行时 未找到源代码 Assert assertEquals 1 contents size 我知道
  • Java ASN.1 编译器

    现在我正在使用二进制笔记 http bnotes sourceforge net 解析 ASN 1 文件以在 Java 项目中使用 它采用 ASN 1 定义并生成 Java 类 让我可以操作 ASN 1 文件 我用扩展标记碰壁了 因为它不支
  • 在这种情况下,我如何处理 Function 和省略号/可变参数?

    我的项目之一是抛出 lambda 表达式 https github com fge throwing lambdas 我的目标是简化潜在的使用 FunctionalInterfaces in Streams 其在流中使用的唯一 缺陷 是它们
  • JP QL - 一对多关系中的过滤结果

    我在尝试构建 JPQL 查询时陷入困境 并希望比我拥有更多 JPA 经验的人能够提供帮助 考虑以下两个实体 class Author String name OneToMany mappedBy author Set
  • 如何将 java ArrayList 转换为等效的 double[] [重复]

    这个问题在这里已经有答案了 可能的重复 如何在 Java 中从 List 转换为 double https stackoverflow com questions 6018267 how to cast from listdouble to
  • 将 Flash 文件上传与 JSF 集成

    我看到我们可以通过flash文件上传来上传多个文件 喜欢SWF上传 http code google com p swfupload or YUI上传器 http yuilibrary com yui docs uploader 是否可以将
  • 阻止 GWT 中的事件冒泡

    我有以下代码片段 changeTextArea 是一个 TextArea 对象 changeTextArea addKeyboardListener new KeyboardListenerAdapter public void onKey
  • CTRL-C 在 Python 中的行为有所不同

    I ve recently started learning Python long time Java programmer here and currently in the process of writing some simple
  • DOM 中不再存在缓存元素

    就像在类似的问题中一样 我使用appium java 尝试选择元素 在移动应用程序中 我要转到页面 之后有许多元素 android widget ImageView 0 我需要选择 6 个 例如 这样的元素并执行其他步骤 Byt 只能选择一
  • 如何使用 Java 1.4 和 SAX 将任意数据编码为 XML?

    我们使用 SAX 来解析 XML 因为它不需要将整个 XML 文档读入内存来解析单个值 我读过很多文章 坚持认为 SAX 只能用于解析 解码 XML 而不能创建它 这是真的 不 这不是真的 您可以使用类似于以下内容的方式将 XML 编码为
  • 异步方法的同步版本

    在 Java 中创建异步方法的同步版本的最佳方法是什么 假设您有一个包含这两种方法的类 asyncDoSomething Starts an asynchronous task onFinishDoSomething Called when
  • 运行外部进程的非阻塞线程

    我创建了一个 Java GUI 应用程序 它充当许多低级外部进程的包装器 该实用程序按原样运行 但迫切需要一项重大改进 我希望我的外部进程以非阻塞方式运行 这将允许我并行服务其他请求 简而言之 我希望能够在生成数据时处理来自外部进程的数据
  • Spring Boot 和安全性以及自定义 AngularJS 登录页面

    我正在为 Spring Security 实现一个自定义 AngularJS 登录页面 但遇到身份验证问题 遵循本教程 示例 以及他们的示例在本地运行良好 https github com dsyer spring security ang
  • Java 和 SQL Server 中的精度噩梦

    我一直在与 Java 和 SQL Server 中的精确噩梦作斗争 直到我不再知道了 就我个人而言 我理解这个问题及其根本原因 但向地球另一端的客户解释这一点是不可行的 至少对我来说 情况是这样的 我在 SQL Server 中有两列 Qt
  • 切换按钮形状不变

    我正在尝试制作一个带有绿色背景的圆形切换按钮 我用了
  • Spring验证非空元素的字符串列表

    我有一个模型类 其中包含字符串列表 该列表可以为空 也可以包含元素 如果它有元素 这些元素不能为空 举个例子 假设我有一个名为 QuestionPaper 的类 它有一个 QuestionId 列表 其中每个都是一个字符串 class Qu

随机推荐

  • ORACLE not available如何解决

    出现Oracle不可用可以一般情况下有两种办法解决 1 先关闭数据库 在打开数据库 SQL gt shutdown immediate SQL gt startup open 先用这种方式看看问题解决了没有 如果没有再用第二种办法试试 2
  • svn服务器 系统重装恢复吗,请教一下好不好把svn版本库还原到以前的版本?

    1 Linux系统安装svn服务 yuminstall subversion2 新建一个目录用于存储SVN所有文件 mkdir p cbroot svnserver cbweb3 在上面创建的文件夹中为项目project 1 创建一个版本仓
  • 操作系统 虚拟存储器的概念

    虚拟存储器 程序装入内存时可能会出现如下问题 程序太大 要求的空间超出了内存总容量 有大量作业要求运行 但内存不能容下所有作业 常规存储器管理方式的特征 一次性 要求作业全部装入内存才能运行 驻留性 许多不用或暂时不用的程序占用了大量内存空
  • linux命令strings

    linux命令strings 其man信息如下 strings 1 GNU Development Tools strings 1 NAME strings 显示文件中的可打印字符 总览 SYNOPSIS strings a all f p
  • 二维线段树【模板——给出对应注释】

    闲话少说 直接看注释反而会更容易读懂这段二维线段树的模板 include
  • elasticsearch启动报错:master not discovered yet

    通过命令启动 bin elasticsearch E node name hotnode E cluster name geektime E path data hot data E node attr my node type hot 报
  • 违反 GPL 协议赔偿 50 万,国内首例!

    整理 祝涛 出品 CSDN ID CSDNnews 近日 一起关于GPL版权纠纷案裁判文书公示 在一审中 法院指出GPL 3 0协议是一种民事法律行为 具有合同性质 可认定为授权人与用户间订立的著作权协议 属于我国 合同法 调整的范围 来源
  • C++ Primer阅读笔记--数组的使用

    1 理解复杂的数组声明 阅读复杂数组声明时 建议由内向外阅读 int ptrs 10 ptrs是一个含有10个整型指针的数组 int refs 10 错误 不存在引用的数组 int Parray 10 arr Parray指向一个含有10个
  • Qt之TCP心跳包

    Qt之TCP心跳包 当Qt作为客户端程序 而服务器需要监控客户端的在线状态时 就需要Qt端发送心跳包 心跳包可以是TCP也可以是UDP 这里介绍TCP心跳包的实现方法 心跳包通常要单开一个线程 在进程运行的过程中一直执行 代码示例 h文件
  • element-ui —Cascader 级联选择器(选中方式处理)

    目前Vue Element的 el cascader 级联选择器 多选或者选择任意一级 需要点击左侧的checkbox才能选中 目标 点击label选中 已选中状态再次点击label取消选中 有两种方式实现 通过添加点击事件 通过css样式
  • 企业微信第三方应用Demo源码

    第三方应用Demo源码 qywx third java qywx third java企业微信开发指南https github com liyuexi qywx guide企业微信开发第三方应用开发视频 https mp weixin qq
  • vue实现滚动监听,锚点定位,导航高亮

  • matlab双立方插值法_双三次插值(bicubic interpolation)原理及MATLAB源码实现

    双三次插值具体实现 clc clear fff imread E Documents BUPT DIP 图片 lena bmp ff rgb2gray fff 转化为灰度图像 mm nn size ff 将图像隔行隔列抽取元素 得到缩小的图
  • pikachu靶场记录之暴力破解-包括带token的密码猜解

    说明 pikachu是一个免费的php靶场 类似于dvwa 从github下载对应的项目 解压缩并放到phpstudy的www目录下即可 在phpstudy软件中开启apache mysql 访问首页 192 168 10 150 pika
  • Gitee在大数据中心的使用

    在本地主机或者可以VSCode直接连接可视化的服务器上 1 首先在gitee新建一个带有develop分支的仓库 2 在自己的主机 e g server 1 3 上git clone下来 例如 git clone git gitee com
  • Flutter ListView详解

    ListView详解 ListView常用构造 ListView ListView 默认构建 效果 ListView ListTile ListTile 属性 ListTile 使用 效果 ListView builder builder属
  • C# combobox绑定数据源(datasource)

    1 绑定数据源 1 1数据源为dataTable DataTable dt new DataTable 显示的数据 ComBox1 DisplayMemeber name name为DataTable的字段名 隐藏的数据 对于多个数据 可以
  • 左连接(LEFT JOIN)无法返回主表所有行的解决方法

    需求 在业务员管理客户页面 需要展示所有客户信息 并且按客户的最近下单次数进行排序 第一次写的代码如下
  • Vue 2 升级Vue3 ,并且使用vsCode 搭建Vue3 开发环境

    Vue 2 升级Vue 3 版本详细步骤 第一 使用快捷键win R 打开cmd 命令窗口 第二 查看当前电脑运行的vue 版本 请使用如下指令 vue V vue Version 卸载目前vue版本 输入如下指令 npm uninstal
  • JAVA常用的七种设计模式

    学习设计模式之前 我们先要了解一下设计模式的怎么来的 对于设计人员 特别是开发人员吗 往往受限于眼界或经验不能够体会到设计原则的实用性 或者在处理具体问题时 不知道如何把设计原则应用到到设计和代码 因此产生了 模式 随着参与的项目越来越多