【进阶】Java设计模式详解

news/2025/2/22 19:11:36/

java注解

什么是注解?

java中注解(Annotation),又称java标注,是一种特殊的注释。

可以添加在包,类,成员变量,方法,参数等内容上面,注解会随同代码被编译到字节码文件中,

在运行时,可以通过反射机制获取到类中注解,然后根据不同的注解进行相应的解析。

内置注解

java中已经定义好的注解

@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。 @SuppressWarnings - 指示编译器去忽略注解中声明的警告。 @FunctionalInterface 用于指示被修饰的接口是函数式接口。

元注解

注解的注解

用来定义其他注解的注解

@Target(ElementType.METHOD)  标注此注解可以作用在哪些内容上面 @Target({ElementType.TYPE,ElementType.MEYHOD}) ​ @Retention(RententionPolicy.SOURCE) 在编译阶段有用的,可以不编译到字节码中 @Retention(RententionPolicy.RUNTIME) 在运行时有用的,编译到字节码中去

自定义注解

java">@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
​String message() default "";int minlength() default 0;String lengthmessage() default "";
}

java">public class User {private int num=0;
​@NotNull(message="姓名不能为空",minlength=3,lengthmessage="长度不能小于3")private String name=null;
​public String getName() {return name;}
​public void setName(String name) {this.name = name;}
​public int getNum() {return num;}
​public void setNum(int num) {this.num = num;}
​
}

java">public class Test {
​public static void main(String[] args) throws NoSuchMethodException, SecurityException, Exception {User user = new User();//user.setName("jim");//通过反射解析User类中注解Field[] fields = user.getClass().getDeclaredFields();//拿到类中所有的成员变量 连同私有的也可以获取//循环所有的属性for (Field field : fields) {NotNull notNull = field.getAnnotation(NotNull.class);//获取属性上面 名字为NotNull注解if (notNull != null) {//通过属性,生成对应的get方法Method m = user.getClass().getMethod("get" + getMethodName(field.getName()));//调用方法  obj就是get方法的返回值Object obj=m.invoke(user);if (obj==null) {System.err.println(field.getName() +notNull.message());throw new NullPointerException(notNull.message());}else{if(String.valueOf(obj).length()<(notNull.minlength())){System.err.println(field.getName() +notNull.lengthmessage());throw new NullPointerException(notNull.lengthmessage());}}}}}
​/*** 把一个字符串的第一个字母大写*/private static String getMethodName(String fildeName) throws Exception {byte[] items = fildeName.getBytes();items[0] = (byte) ((char) items[0] - 'a' + 'A');return new String(items);}
}

对象克隆

什么是对象克隆?

在已有的对象基础,克隆出一个新的对象,并将已有对象中的属性值,复制到新克隆出来的对象中。

为什么用到对象克隆

经常在项目开发中,各层数据传输时,需要将一层中的数据,封装到另一个对象中去。

java中如何实现一个基本的对象克隆
java">public class Person implements  Cloneable{int num;String name;@Overrideprotected Person clone() throws CloneNotSupportedException {Person person = (Person)super.clone();return person;}
}    
​
测试Person p1 = new Person(100,"jim");Person p2 =  p1.clone(); //克隆一个新的对象
​System.out.println(p1==p2);//false  说明是两个不同的对象

深克隆/浅克隆
浅克隆

对于基本类型,在对象克隆时,可以将值直接复制到新对象中。

浅克隆主要是在于对象中关联的另一个对象是否能被克隆出一个新的对象,

如果克隆时,只是将关联对象的地址进行复制,那么就属于浅克隆

如果克隆时,将关联的对象也一同克隆出了一个新的对象,那么就属于深克隆

如何实现深克隆

方式1:在克隆对象时,将对象中关联的对象也一同进行克隆。虽然能实现,但是要逐级进行克隆,层级比较多时,比较麻烦。

方式2:使用对象序列化 IO

需要我们自己定义一个克隆方法,先将对象进行序列化,然后在进行反序列化,自动将多级关联的对象也一并重新创建,使用起来比较方便,注意需要序列化的类 必须要实现Serializable接口

对象序列化

java中的对象,输出到一个文件中

ObjectOutputStream

反序列化

将文件中的信息输入到程序中,创建一个新的对象

ObjectInputStream

创建对象的几种方式:

1.new对象

2.反序列化

3.反射机制

4.对象克隆

深克隆方式2核心实现代码

java"> /*** 自定义克隆方法* @return*/public Person myclone() {Person person = null;try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(this);// 将流序列化成对象ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);person = (Person) ois.readObject();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return person;}

Java设计模式

设计模式产生背景

设计模式概念首先起源于建筑领域

1990在软件领域也诞生了设计模式概念。直到1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等4 位作者合作出版了《设计模式:可复用面向对象软件的基础》一书,在此书中收录了23 个设计模式

什么是设计模式

在长期编程的过程中,针对某一类问题经过反复的优化,最终总结出一个固定的解决方案,这些方案经过反复的使用,具有普遍性。

为什么要学习设计模式

学习设计模式就是学习好的编程思想,学习前辈们的经验。

可以提高程序员的思维能力、编程能力和设计能力。 ​ 使程序设计更加标准化、使软件开发效率大大提高。 ​ 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。 ​ 能够更好的去理解源码架构

建模语言

统一建模语言(Unified Modeling Language,UML),是一套软件设计和分析的语言工具。

用图形化的方式,记录表示类与类,类与接口,接口与接口之间的关系,一般把图形化方式也称为UML类图。

类图中两个基本的要素:

1.类

2.接口

类与类之间的关系

1.依赖关系

在一个类中的方法,把另一个类作为参数进行使用,具有临时性,

方法执行结束后依赖关系就不存在了。

一般把xxx类用到了xxx类这种关系称为依赖关系,也称为use-a关系

2.关联关系 has-a关系 谁有什么

在一个类中,把另一个类当做自己的成员.

单向关联,双向关联,自关联

一对一关联,一对多关联

关联关系根据强弱又分为聚合和组合

3.聚合关系

聚合关系也是一种关联关系,是一种整体和部分之间的关系.

学校包含老师,即使学校不存在了,老师可以依然独立的存在

4.组合关系

组合关系也是一种关联关系,是一种更强烈的关联关系

头和嘴关系,头如果不存在了,嘴也会跟着销毁

5.继承关系


6.实现关系


面向对象设计原则

单一职责原则

是实现高内聚、低耦合的指导方针

一个类只负责某一个具体的功能,细化类的粒度。

开闭原则

对修改关闭,对扩展开放

尽可能的在扩展功能时,不要修改已有的代码,尽可能扩展一个新的类来实现新功能。

里氏替换原则

继承优势:提高代码复用性 子类继承父类的功能

提高代码的扩展性 子类还可以扩展自己的功能 不影响其他类 重写父类方法

继承劣势:继承使得类的体系结构变得复杂了

里氏替换

首先是由里斯科夫女士提供的,

其次是关于继承使用的,

当子类继承了父类后,在使用时,用子类替换父类后,要确保父类中的功能不受影响。

主要的思想:就保证程序的稳定性

依赖倒置

面向抽象编程 不要面向具体的实现编程

具体实现应该依赖抽象层 (多态 抽象层表示 具体的子实现类)

接口隔离原则

不要把所有的功能都定义到一个总的接口中,应该把不同的种类的功能,定义在不同的接口中,让实现类,根据自己的需要去灵活的选择。

迪米特原则

只跟朋友联系,不跟“陌生人”说话。

在程序间相互调用时,如果两个类没有直接联系,但是又想相互调用,可以通过第三方进行转发调用。

组合/聚合(关联关系)复用原则

继承使得类的体系变得复杂,如果我们只是想使用某个类中的方法时,

也可以优先选择关联关系/依赖关系 降低类与类之间的耦合度

23种设计模式分类

分为3大类

创建型模式

结构型模式

行为性模式

单例模式

在一个项目中,如何确保一个类始终只有一个对象。

单例模式有3 个特点:

  1. 单例类只有一个实例对象;

  2. 该单例对象必须由单例类自行创建;

  3. 单例类对外提供一个访问该单例的全局访问点;

 
java">/*单例模式中的饿汉式(急切式单例)在加载此类时,就已经将唯一的一个对象创建出来好处:不会存在线程安全问题不足: 在类加载时,就会创建单例对象,有可能一段时间内还用不到它*/
public class MyWindow {//在内部自己创建的一个单例对象private static MyWindow myWindow = new MyWindow();private MyWindow(){}/*对外提供这唯一的对象*/public static  MyWindow getMyWindow(){return myWindow;}
}
java">/*懒汉式单例  在类加载时,并没有创建单例对象,在第一次获取单例对象时,才去创建了单例对象好处: 类加载时先不创建,在第一次使用获取时才会创建不足: 会出现线程安全问题加锁解决*/
public class MyWindow {private volatile static MyWindow myWindow;/*构造方法私有化,不让外界访问*/private MyWindow(){}/*写法1: 会出现线程安全问题,多个线程同时进入,会返回多个对象public static  MyWindow getMyWindow(){if(myWindow==null){myWindow = new MyWindow();}return myWindow;}*/
​/*写法2: 为方法加锁效率低  一次只能有一个线程进入到该方法
​public static synchronized   MyWindow getMyWindow(){if(myWindow==null){myWindow = new MyWindow();}return myWindow;}*/
​/*写法3: 双重检索
​public static    MyWindow getMyWindow(){
​if(myWindow==null){
​synchronized (MyWindow.class){
​if(myWindow==null){
​myWindow = new MyWindow();}}}return myWindow;} */
​/*写法4: 双重检索 + volatile(可见性,避免重排序)A a = new A();创建对象这一条语句编译为指令时,可以分为三个指令1. new 申请空间2. 调用构造方法初始化对象3. 把对象地址 赋给引用变量如果按照这个正常的顺序执行,是没有问题的,但是执行时,如果2,3条指令顺序发生变化,导致把没有初始化完成的对象地址返回了,拿去使用了,这么做会出问题,因为对象没有初始化完成.所有需要使用volatile关键修饰单例成员变量,确保对其赋值时,指令不重新排序*/public static    MyWindow getMyWindow(){if(myWindow==null){synchronized (MyWindow.class){if(myWindow==null){myWindow = new MyWindow();}}}return myWindow;}

工厂模式

解决的就是在项目将创建对象和使用对象分离的问题(结合Spring)

更好的组织类与类之间的关系

简单工厂模式

简单工厂并不是一种设计模式,违背了开闭原则

主要是引出工厂方法和抽象工厂模式

涉及的角色

工厂角色:根据我们的需求,创建对应的对象

抽象产品:具体产品的抽象,具体产品实现/继承抽象产品,

可以使用上层的抽象父类,表示任意的子类对象

具体产品:具体的对象

优点:创建对象和使用对象分离了

缺点:只能创建出现了同一个父类/接口的子类对象,扩展新的类型,需要修改工厂,违背了开闭原则。

适合简单的,子类较少的场景

工厂方法模式

由于简单工厂中,一个工厂,可以造同一类型的所有具体产品,导致简单工厂比较复杂,扩展一个新类型时,需要修改工厂代码。

工厂方法模式,为工厂也进行抽象,并且为同类型产品都创建了一个具体的工厂。

每一个工厂负责创建一个具体的产品(类型)对象,这样扩展一个新的类型,与之对应一个工厂,就不需要修改工厂,遵守了开闭原则,单一职责原则

抽象工厂模式

工厂方法模式,是按照产品类型进行分类的,一类产品,对应一类工厂, 不同类型产品之间,相互隔离的

例如 华为和小米 既要汽车,又要造手机,都是属于同一家的产品,

但是工厂方法这种设计,同一个公司产品与产品之间没有联系.

抽象工厂对工厂重新进行分类,

以公司为单位进行工厂的抽象(提取), 一个工厂内,可以创建不同的产品,

这样我们就可以创建出像华为工厂,小米工厂这样的具体工厂,

一个工厂内,可以创建不同公司的各种产品

原型模式

在某些场景下, 为避免自己手动的new对象,我们可以使用对象克隆方式,创建并返回一个新的对象

这种克隆新对象的效率比我们自己new的效率要高.

对象克隆实现方式有两种:

1.实现Cloneable接口,重写clone()

2.使用对象序列化 反序列化重新生成对象

注意深克隆和浅克隆问题.

代理模式

早在spring aop思想中,已经用到了代理思想.

在不修改原来代码的前提下,为我们方法添加额外的功能.

通过代理对象帮助我们进行调用。

有些时候,目标对象(汽车厂)不想或不能直接与客户打交道,通过代理对象进行访问,

代理对象可以保护目标对象,对目标对象功能进行扩展, 降低了模块之间的耦合度

涉及到三个主题:

抽象主题: (抽取的功能,让目标对象进行实现,以及代理对象进行实现)

具体主体: 真正要实现功能的类

代理对象

代理模式实现方式又有两种:

1.静态代理

创建一个代理类, 代理实现与具体对象相同的接口/抽象类,重写抽象方法

还有一个成员变量,可以用接收具体的主题,

在代理对象中重写的抽象方法中 调用真实主题方法, 这样就可以在调用之前和之后添加额外的功能.

不好地方: 一个代理对象,只能代理一个接口类型的对象 不灵活

2.动态代理

动态代理实现分为两种:

1.jdk代理

jdk代理实现是通过反射机制实现的, 目标类必须要实现一个接口,通过接口动态获得目标类中的信息,

2.cglib代理

是spring中提供的一种代理技术, 目标类可以不实现任何接口

采用字节码生成子类的方式,对方法进行拦截,实现机制不同.

cglib不能代理final修饰的类 以及fnal和static修饰的方法

目前spring中两种动态代理都支持, 如果目标类没有实现接口,默认使用cglib代理

如果目标类有实现接口,采用jdk代理

模板方法模式

JdbcTemplate 执行sql时,步骤也是固定的, 1.链接数据库 2发送sql 3.提交事务,关闭链接

模版方法模式, 是在一个类中,定义好一个算法骨架,设定好实现步骤, 把一些公共的通用的在父类中实现.

然后将一些不确定的实现在具体的子类中实现.

结构:

抽象类:

模版方法: 定义好执行顺序的算法骨架,确定好执行流程顺序

抽象方法: 不确定的功能,定义为抽象的,交给子类实现

具体方法: 都一样的公共的通用的方法,在抽象父类中实现

具体子类: 实现抽象类中的抽象方法的具体类, 有不同的实现方式,就可以用多个子类.

new 具体子类对象, 用具体子类对象调用模版方法,把父类中具体方法与自己实现的抽象方法一起执行

将变化部分定义为抽象的,让子类去扩展实现, 满足开闭原则

适合流程相对比较固定的,其中有变化的场景

策略模式

将不同的实现算法进行封装, 将功能的实现与使用相分离,

在使用时,可以用不同的策略实现类进行替换. 重点用到的知识点, 继承 多态

结构

抽象类: 抽象放方法

具体实现类

环境类(使用者)

抽象 表示任意的子类

你对java语言认识

开源

跨平台的

简单易学 抛弃了指针

安全

支持多线程

支持网络

支持垃圾回收

丰富生态

最重要的是, 还是面向对象的

你对面向对象的认识

回答方式多样,每个人的理解角度不同, 举例不同, 没有一种标准回答方式

从面向过程谈起, 面向过程语言是如何解决问题的

但是面向对象又是怎么来解决问题,

分类

类 把同一类的事务定义到一个类中, 解决某一类问题,找对应的类即可.

通过类可以实例化出 具体的对象 , 类是模版 对象是具体存在, 使用的是创建出来的具体对象.

面向对象语言还有三大特征

封装 使用访问权限修饰符,对成员进行修饰, 控制内部成员是否对外可见

继承 子 继承 父 继承父类中 非私有的功能 实现代码的复用 和 扩展(定义自己特有的方法, 重写父类方法)

多态 同一种事物,在不同时刻表现不同的状态 父类类型可以表示任意的子类类型 提高程序的扩展性

遵守相关的面向对象设计原则和设计模式 使得程序设计更加强大(复用,灵活,扩展,.....)

接口 抽象类

static

方法重载 方法重写


http://www.ppmy.cn/news/1574233.html

相关文章

《重构-》

一、代码坏的味道 神秘命名 ​​​​​代码应该直观明了。要深思熟虑如何给函数、模块、变量和类命名&#xff0c;使它们能清晰地表明 自己的功能和用法。 重复代码 一旦有重复代码存在&#xff0c;阅读这些重复的代码时你就必须加倍仔细&#xff0c;留意其间细微的差异。如果…

EasyExcel 自定义头信息导出

需求&#xff1a;需要在导出 excel时&#xff0c;合并单元格自定义头信息(动态生成)&#xff0c;然后才是字段列表头即导出数据。 EasyExcel - 使用table去写入&#xff1a;https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E4%BD%BF%E7%94%A8table%E…

TikTok账户安全指南:如何取消两步验证?

TikTok账户安全指南&#xff1a;如何取消两步验证&#xff1f; 在这个数字化的时代&#xff0c;保护我们的在线账户安全变得尤为重要。TikTok&#xff0c;作为全球流行的社交媒体平台&#xff0c;其账户安全更是不容忽视。两步验证作为一种增强账户安全性的措施&#xff0c;虽…

高斯积分的证明

内容来源 B站视频BV1LC4y1P7gM 高斯积分 ∫ 0 ∞ e − x 2 d x \int^\infty_0e^{-x^2}\mathcal{d}x ∫0∞​e−x2dx 添加新元 设 f ( t ) [ ∫ 0 t e − x 2 d x ] 2 f(t)\left[\int^t_0e^{-x^2}\mathcal{d}x\right]^2 f(t)[∫0t​e−x2dx]2 现目标 求 lim ⁡ t → ∞ f …

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_test_full_name

ngx_test_full_name 声明在 src\core\ngx_file.c static ngx_int_t ngx_test_full_name(ngx_str_t *name); 定义在 src\core\ngx_file.c static ngx_int_t ngx_test_full_name(ngx_str_t *name) { #if (NGX_WIN32)u_char c0, c1;c0 name->data[0];if (name->len <…

SPRING10_SPRING的生命周期流程图

经过前面使用三大后置处理器BeanPostProcessor、BeanFactoryPostProcessor、InitializingBean对创建Bean流程中的干扰,梳理出SPRING的生命周期流程图如下

✨2.快速了解HTML5的标签类型

✨✨HTML5 的标签类型丰富多样&#xff0c;每种类型都有其独特的功能和用途&#xff0c;以下是一些常见的 HTML5 标签类型介绍&#xff1a; &#x1f98b;结构标签 &#x1faad;<html>&#xff1a;它是 HTML 文档的根标签&#xff0c;所有其他标签都包含在这个标签内&am…

向 OpenAI ChatGPT 提问如何学习黑客

Ailx10 互联网行业 安全攻防员 ​目录 如何学习黑客攻击&#xff1f; 如何学习计算机网络安全&#xff1f; 如何学习渗透测试&#xff1f; 如何学习Cobalt Strike &#xff1f; OpenAI ChatGPT 是一个人工智能回答问题系统&#xff0c;最近非常火&#xff0c;以至于很多…