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常用的七种设计模式 的相关文章

  • 如何使用 Java 中的 Web 服务(例如 Axis2)发送复杂对象的数组或集合?

    我对 SOAP Web 服务还比较陌生 虽然我完成了一些较小的 Web 服务项目 但我偶然从来不需要返回 或用作参数 复杂 对象的数组或集合 当我尝试这样做时 根据我的 SOAP 绑定风格 我会得到不同的奇怪行为 当我使用RPC 文字 我可
  • 插入最大日期(独立于数据库)

    在我的本地设置中 我使用一个简单的 H2 数据库 托管 解决方案将有另一个 类似但不相同 数据库 我需要将最大可能日期插入到日期时间列中 我尝试使用 Instant MAX 但是 这会导致列中出现 169104626 12 11 20 08
  • Java:迭代 Collection 的最佳方法(此处为 ArrayList)

    今天 当我看到一段我已经使用了数百次的代码时 我很高兴地开始编码 迭代集合 此处为 ArrayList 出于某种原因 我实际上查看了 Eclipse 的自动完成选项 这让我想知道 在什么情况下以下循环比其他循环更好使用 经典的数组索引循环
  • SAML 服务提供商 Spring Security

    当使用预先配置的服务提供者元数据时 在 Spring Security 中 是否应该有 2 个用于扩展元数据委托的 bean 定义 一份用于 IDP 元数据 一份用于 SP 元数据
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • JAVA - Xuggler - 组合 MP3 音频文件和 MP4 电影时播放视频

    使用 JAVA 和 Xuggler 以下代码组合 MP3 音频文件和 MP4 电影文件并输出组合的 mp4 文件 我希望在合并音频和视频文件时应自动播放输出视频文件 String inputVideoFilePath in mp4 Stri
  • 如何在不超过最大值的情况下增加变量?

    我正在为学校开发一个简单的视频游戏程序 我创建了一个方法 如果调用该方法 玩家将获得 15 点生命值 我必须将生命值保持在最大值 100 并且由于我目前的编程能力有限 我正在做这样的事情 public void getHealed if h
  • 如何安全地解决这个 Java 上下文类加载器问题?

    我的数百名用户中只有一位在启动我的 Java 桌面应用程序时遇到问题 他只有大约三分之一的时间开始 另外三分之二的时间在启动时抛出 NullPointerException Exception in thread AWT EventQueu
  • 画透明圆,外面填充

    我有一个地图视图 我想在其上画一个圆圈以聚焦于给定区域 但我希望圆圈倒转 也就是说 圆的内部不是被填充 而是透明的 其他所有部分都被填充 请参阅这张图片了解我的意思 http i imgur com zxIMZ png 上半部分显示了我可以
  • Calendar.getInstance(TimeZone.getTimeZone("UTC")) 不返回 UTC 时间

    我对得到的结果真的很困惑Calendar getInstance TimeZone getTimeZone UTC 方法调用 它返回 IST 时间 这是我使用的代码 Calendar cal Two Calendar getInstance
  • 具有 java XSLT 扩展的数组

    我正在尝试使用 java 在 XSLT 扩展中使用数组 我收到以下错误 Caused by java lang ClassCastException org apache xpath objects XObject cannot be ca
  • Java 中的“Lambdifying”scala 函数

    使用Java和Apache Spark 已用Scala重写 面对旧的API方法 org apache spark rdd JdbcRDD构造函数 其参数为 AbstractFunction1 abstract class AbstractF
  • react-native run-android 失败并出现错误:任务 ':app:dexDebug' 执行失败

    我使用的是 Windows 8 1 和react native cli 1 0 0 and react native 0 31 0 添加后react native maps对于该项目 我运行了命令react native upgrade并给
  • Struts 2 + Sitemesh 3 集成 - FreemarkerDecoratorServlet 中的 NPE

    我将 Struts 2 版本 2 3 14 3 与 Sitemesh 3 版本 3 0 alpha 2 一起使用 并且在某些情况下遇到 NullPointerException 首先 这是我的 web xml 中的 struts2 site
  • java.lang.NumberFormatException: Invalid int: "3546504756",这个错误是什么意思?

    我正在创建一个 Android 应用程序 并且正在从文本文件中读取一些坐标 我在用着Integer parseInt xCoordinateStringFromFile 将 X 坐标转换为整数 Y 坐标的转换方法相同 当我运行该应用程序时
  • Cucumber Java 与 Spring Boot 集成 - Spring @Autowired 抛出 NullPointer 异常

    我正在为 Spring boot 应用程序编写 cucumber java 单元测试来测试每个功能 当我与 Spring Boot 集成时 Autowired 类抛出 NullPointer 异常 Spring Boot应用程序类 Spri
  • spring中如何使用jackson代替JdkSerializationRedisSerializer

    我在我的一个 Java 应用程序中使用 Redis 并且正在序列化要存储在 Redis 中的对象列表 但是 我注意到使用 RedisTemplate 会使用 JdkSerializationRedisSerializer 相反 我想使用 J
  • 调整添加的绘制组件的大小和奇怪的摆动行为

    这个问题困扰了我好几天 我正在制作一个特殊的绘画程序 我制作了一个 JPanel 并添加了使用 Paint 方法绘制的自定义 jComponent 问题是 每当我调整窗口大小时 所有添加的组件都会 消失 或者只是不绘制 因此我最终会得到一个
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话
  • GUI Java 程序 - 绘图程序

    我一直试图找出我的代码有什么问题 这个想法是创建一个小的 Paint 程序并具有红色 绿色 蓝色和透明按钮 我拥有我能想到的让它工作的一切 但无法弄清楚代码有什么问题 该程序打开 然后立即关闭 import java awt import

随机推荐

  • 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常用的七种设计模式

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