常用设计模式全面总结版(JavaKotlin)

这篇文章主要是针对之前博客的下列文章的总结版本:

  • 《设计模式系列学习笔记》
  • 《Kotlin核心编程》笔记:设计模式
  • 【Android知识笔记】FrameWork中的设计模式

主要为了在学习了 Kotlin 之后,将 Java 的设计模式实现与 Kotin 的实现放在一起做一个对比。

一、创建型模式

单例模式(Java)

Double Check Lock + volatile 版本:

public class Singleton { /** 使用 volatile 保证对该变量的写入对另一个线程可见 */private volatile static Singleton mSingleton = null;private Singleton() {}public static Singleton getInstance() {if (mSingleton == null) {synchronized(Singleton.class) {if (mSingleton == null) {mSingleton = new Singleton();}}}return mSingleton;}
}

为什么要 Double Check Lockvolatile,因为 mSingleton = new Singleton() 这一句翻译成字节码指令可能对应多条,它不是一次性完成的原子性操作,会分成三个步骤完成:

  1. 给实例分配内存,
  2. 调用构造函数初始化实例,
  3. 给变量赋值操作。

这三个步骤中后面两步可能发生指令重排序,即顺序为1-3-2,从而导致双重锁定失效,另一个线程同时访问就会拿到一个还没来得及初始化完毕的对象实例,出现问题。

volatile 在这里的主要作用是禁止指令重排序。另外 volatile 还可以保证线程可见性(但不保证原子性)。

静态内部类版本:

public class Singleton {private Singleton(){}/*** 静态内部类,内部类的实例与外部类的实例没有绑定关系,* 只有被调用到时才会加载,从而实现延迟加载*/private static class SingletonHolder {/** 静态初始化器,由 JVM 类加载过程来保证线程安全 */private static Singleton instance = new Singleton();}public static Singleton getInstance() {return SingletonHolder.instance;}
}

注意这个写法,只有getInstance()方法是public的,其他静态内部类及其内部静态变量都是private的。

getInstance方法第一次被调用的时候,会导致JVM 虚拟机加载 SingletonHolder 类,它的静态成员变量得到初始化;从而创建 Singleton 的实例。

补充:JVM加载类的过程:加载 -> 验证 -> 准备 -> 解析 -> 初始化

准备阶段会为SingletonHolder中的静态成员instance赋值为null,在编译时会收集静态赋值语句等组成类构造器<cinit>(),在初始化阶段会执行类构造器来初始化一个类。

触发 JVM 初始化一个类有以下几种情况:

  • 使用new关键字创建对象
  • 访问类的静态成员变量 或 对类的静态成员变量进行赋值
  • 调用类的静态方法
  • 反射调用类时,如 Class.forName()
  • 初始化子类时,会先初始化其父类(如果父类还没有进行过初始化的话)
  • 遇到启动类时,如果一个类被标记为启动类(即包含main方法),虚拟机会先初始化这个主类。
  • 实现带有默认方法的接口的类被初始化时(拥有被default关键字修饰的接口方法的类)
  • 使用 JDK7 新加入的动态语言支持时 MethodHandle

显然当调用Singleton.getInstance() -> SingletonHolder.instance 时,命中上面第 2 条,JVM 就会执行 SingletonHolder 这个类的初始化,然后调用其类构造器<cinit>(),这个时候就会初始化Singleton instance静态成员对象的实例。

在这个过程中, JVM 会保证SingletonHolder这个类的<clinit>()方法被正确的加锁同步,在多线程访问的情况下,只会有一个线程看到这个类的初始化过程,而其他线程是不可见的, 其它线程都需要等待,直到<clinit>()方法执行完毕。所以是线程安全的。

这种方式既保证了线程安全性,又保证了对象的唯一性。并且还是懒加载,只有被调用时才会初始化。

枚举单例:

public enum Singleton {INSTANCE;public void doSomething() {// todo}
}

Android源码中的单例:

  • context.getSystemService(name)context的实现类 ContextImpl 中使用静态的Map对象存储ServiceFecther对象(恶汉)
  • 线程内的单例ThreadLocal,如 ChoreographerLooper 中都使用 ThreadLocal 绑定当前线程,同一个 ThreadLocal 对象在同一个线程内返回的对象是唯一的。
  • 进程内的单例:如 ActivityManager 类的 getService() 方法返回的 IActivityManagerSingleton 单例在进程内唯一。
  • 进程间的单例ServiceManager,不同进程之间内存是隔离的,在进程间共享的单例其实是由binder驱动来保证不同的进程共用同一个对象的。对所有的进程而言,ServiceManager对应同一个binder句柄(通过 0Handle句柄引用),binder句柄处理完不同进程的请求后会转发给ServiceManager所在的进程。

Q:怎样在应用里做一个跨进程的单例?

  • 单例对象类必须实现Parcelable接口,成员变量除了基本数据类型外都必须实现Parcelable接口
  • 通过 AIDL 接口, 拿到远端进程的IBinder对象,再进行 Binder 调用远端接口的业务方法,将单例对象作为参数发布给远端进程
  • 远端进程在业务方法中将接受到单例对象保存下来使用

单例模式(kotlin)

在 kotlin 中 object 类是天生的单例模式。

object Singleton {var x: Int = 2 fun y() { }
}

这个代码翻译成 Java 字节码后发现它其实就是恶汉模式的单例

如果是一个普通类想生成单例,可以使用伴生对象 companion object 来生成:

class Singleton {companion object {var x: Int = 2fun y() { }}
}

如果要实现 Java 中静态内部类版本的单例模式,可以像下面这样写:

class Singleton private constructor() {private object Holder {val INSTANCE = Singleton()}companion object {val instance = Holder.INSTANCE}
}

object不能定义构造函数,但可以定义init块:

object Singleton {var x: Int = 2 fun y(){ }init {//object不能定义构造函数,但可以定义init块}
}

如果要在Java中使用kotlin的单例,最好在成员属性和成员方法上分别加上 @JvmField@JvmStatic注解:

object Singleton {@JvmField var x: Int = 2  @JvmStatic fun y() { } 
}
  • @JvmField 的作用是生成java的静态成员 不会生成gettersetter方法。
  • @JvmStatic 的作用是生成java的静态方法。

object修饰的类内部方法相当于静态方法,但是伪静态,也就是内部会生成一个静态的成员对象,对类的方法调用实际上是调用的内部静态成员对象的方法,只有在方法上添加@JvmStatic才会真正的生成静态方法。

普通kotlin类(非单例)中使用使用JvmFieldJvmStatic

class Foo {//普通类中使用JvmField和JvmStaticcompanion object {@JvmField var x: Int = 2@JvmStatic fun y(){  }}
}

注意,加的注解@JvmField@JvmStatic只针对java平台的可用,其他平台并不可行。

单例的object类仍可以继承类:

object Singleton: Runnable{override fun run() {}
}

工厂方法模式(Java)

  • 定义了一个创建对象的接口,但是由子类来决定要实例化的是哪一个类。工厂方法使类的实例化延迟到子类中。
public abstract class Product {public void method1() {}public abstract void method();
}
public class ConcreteProductA extends Product {@Overridepublic void method2() {//ProductA的业务处理}
}
public class ConcreteProductB extends Product {@Overridepublic void method2() {//ProductB的业务处理}
}
public abstract class Factory {/** * 此方法必须返回一个Product类型的对象*/public abstract <T extends Product> T createProduct(Class<T> c);
}
public class ConcreteFactory extends Factory { @Overridepublic <T extends Product> T createProduct(Class<T> c) {//创建Product的逻辑,这里是直接调用Class的newInstance方法Product product = null;try {product = (Product) Class.forName(c.getName()).newInstance();} catch (Exception e) {e.printStackTrace();}return (T)product;}
}
public class Client {public static void main(String[] args) {Creator creator = new ConcreteCreator();Product productA = creator.createProduct(ConcreteProductA.class);Product productB = creator.createProduct(ConcreteProductB.class);......}
}

简单工厂模式:如果实际当中只需要一个工厂类,那么就不需要抽象的工厂基类,可以把创建产品的方法改为静态方法

public class HumanFactory {@Overridepublic static <T extends Human> T createHuman(Class<T> c) {Human human = null;try {human = (Human) Class.forName(c.getName()).newInstance();} catch (Exception e) {e.printStackTrace();}return (T) human;}
}

多个工厂类:在实际当中如果初始化创建对象的过程比较复杂,比如不同的产品可能会设置不同的参数,这个时候创建产品的方法不能共用,所以这时就需要为每一个产品类创建一个工厂类。

可生成单例模式的工厂类:既然工厂方法是用来生产对象的,那么就可以对工厂方法做简单的修改,只返回一个对象,就变成了单例模式。

public class Singleton {//不允许通过new产生对象private Singleton(){}public void doSomething() {//业务处理}
}public class SingletonFactory {private static Singleton singleton;static {try {Class<?> clazz = Class.forName(Singleton.class.getName());//获取无参的构造函数Constructor<?> constructor = clazz.getDeclaredConstructor();//设置无参的构造函数可访问constructor.setAccessible(true);//创建一个实例对象singleton = (Singleton) constructor.newInstance();} catch (Exception e) {e.printStackTrace();}}public static Singleton getSingleton() {return singleton;}
}

可复用缓存的工厂类:如果创建对象的过程比较复杂,或者非常耗时,可以在工厂类内部对已创建的对象进行缓存,以备下次使用。

public class ProductFactory {private static final Map<String, Product> productMap = new HashMap<>();public static synchronized Product createProduct(String type) {Product product = null;//如果缓存中有该对象实例直接使用if (productMap.containsKey(type)) {product = productMap.get(type);} else {if (type.equals("ProductA")) {product = new ConcreteProductA();} else {product = new ConcreteProductB();}//把创建的对象缓存起来productMap.put(type, product);}return product;}
}

Android源码中的工厂方法模式:

  • 集合类的iterator()返回的是一个新的迭代器对象,其实就是一个工厂方法。
  • ActivityonCreate()方法某种角度上可以看作是一个用于创建ContentView的工厂方法,该ContentView用于填充PhoneWindowDecorView的内容区。

抽象工厂模式(Java)

  • 为创建一组相关或依赖的对象提供一个接口,而无需指定它们的具体类。

在这里插入图片描述

/**
* 抽象工厂
*/
public interface AbstractFactory {/**创建A产品家族的产品*/public AbstractProductA createProductA();/**创建B产品家族的产品*/public AbstractProductB createProductB();
}
public class ConcreteFactory1 implements AbstractFactory {@Overridepublic AbstractProductA createProductA() {//创建一个来自A产品家族的产品return new ProductA1();}@Overridepublic AbstractProductB createProductB() {//创建一个来自B产品家族的产品return new ProductB1();}
}
public class ConcreteFactory2 implements AbstractFactory {@Overridepublic AbstractProductA createProductA() {//创建一个来自A产品家族的产品return new ProductA2();}@Overridepublic AbstractProductB createProductB() {//创建一个来自B产品家族的产品return new ProductB2();}
}
/**
* 产品A家族
*/
public abstract class AbstractProductA {/**每个产品共有的方法*/public void shareMethod() {}/**每个产品不同实现的方法*/public abstract void doSomething();
}
public class ProductA1 extends AbstractProductA {@Overridepublic void doSomething() {System.out.println("产品A1的实现方法");}
}
public class ProductA2 extends AbstractProductA {@Overridepublic void doSomething() {System.out.println("产品A2的实现方法");}
}
/**
* 产品B家族
*/
public abstract class AbstractProductB {/**每个产品共有的方法*/public void shareMethod() {}/**每个产品不同实现的方法*/public abstract void doSomething();
}
public class ProductB1 extends AbstractProductB {@Overridepublic void doSomething() {System.out.println("产品B1的实现方法");}
}
public class ProductB2 extends AbstractProductB {@Overridepublic void doSomething() {System.out.println("产品B2的实现方法");}
}
public class Client {public static void main(String[] args) {//创建两个工厂AbstractFactory factory1 = new ConcreteFactory1();AbstractFactory factory2 = new ConcreteFactory2();//使用factory1来创建一组产品,它们来自不同的产品家族AbstractProductA productA1 = factory1.createProductA();AbstractProductB productB1 = factory1.createProductB();//使用factory2来创建一组产品,它们来自不同的产品家族AbstractProductA productA2 = factory2.createProductA();AbstractProductB productB2 = factory2.createProductB();//do something...}
}

抽象工厂模式其实就是每个工厂类是为生产某一类相关性很强的产品类族专门特供的版本

在这里插入图片描述

工厂方法模式(Kotlin)

用单例代替工厂类

我们已经知道 Kotlin 支持用 object 来实现 Java 中的单例模式。所以我们可以使用一个 object 单例来代替一个工厂类。

object ComputerFactory { // 用 object 代替 classfun produce(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}
}
fun main() {val compter = ComputerFactory.produce(ComputerType.PC)println(compter.cpu)
}

我们可以通过operator操作符重载invoke方法来代替produce,从而进一步简化表达:

object ComputerFactory {   operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}
}
fun main() {val compter = ComputerFactory(ComputerType.PC)println(compter.cpu)
}

伴生对象创建静态工厂方法

interface Computer {val cpu: Stringcompanion object {operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}}
}class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computerenum class ComputerType { PC, Server }fun main() {val compter = Computer(ComputerType.PC)println(compter.cpu)
}

这样就可以在一个接口类中添加伴生对象的方式来创建静态工厂方法。

伴生对象也可以指定名字:

interface Computer {val cpu: Stringcompanion object Factory {operator fun invoke(type: ComputerType): Computer {return when (type) {ComputerType.PC -> PC()ComputerType.Server -> Server()}}}
}fun main() {val compter = Computer.Factory(ComputerType.PC)println(compter.cpu)
}

为伴生对象添加扩展方法

主要是针对三方类库中无法直接修改源码的类。

fun Computer.Companion.fromCPU(cpu: String): ComputerType? = when(cpu) {"Core" -> ComputerType.PC"Xeon" -> ComputerType.Serverelse -> null
}

指定伴生对象名字的写法:

fun Computer.Factory.fromCPU(cpu: String): ComputerType? = when(cpu) {"Core" -> ComputerType.PC"Xeon" -> ComputerType.Serverelse -> null
}

调用:

fun main() {val type = Computer.fromCPU("Core")println(type)
}

抽象工厂模式(Kotlin)

Kotlin 中的可以使用内联函数 inline + reified 关键字来简化抽象工厂模式:

class Dell: Computer { }
class Asus: Computer { }
class Acer: Computer { }class DellFactory: AbstractFactory() {override fun produce() = Dell()
}
class AsusFactory: AbstractFactory() {override fun produce() = Asus()
}
class AcerFactory: AbstractFactory() {override fun produce() = Acer()
}abstract class AbstractFactory {abstract fun produce(): Computercompanion object {inline operator fun <reified T : Computer> invoke(): AbstractFactory = when(T::class) {Dell::class -> DellFactory()Asus::class -> AsusFactory()Acer::class -> AcerFactory()else -> throw IllegalArgumentException()}}
}fun main() { val dellFactory = AbstractFactory<Dell>()val dell = dellFactory.produce()println(dell)
}

建造者模式(Java)

  • 封装一个产品的构建过程,并允许按步骤构造。
  • 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
public class Robot {private final String code;private final String battery;private final Integer height;private final Integer weight;private Robot(String code, String battery, Integer height, Integer weight) {this.code = code;this.battery = battery;this.height = height;this.weight = weight;}public static class Builder {private final String code;private String battery;private Integer height;private Integer weight;public Builder(String code) {this.code = code;}public Builder setBattery(String battery) {this.battery = battery;return this;}public Builder setHeight(int height) {this.height = height;return this;}public Builder setWeight(int weight) {this.weight = weight;return this;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何解决企业内部FTP文件传输速度过慢和安全问题

在数据化时代里&#xff0c;企业内部的文件传输永远是刚需&#xff0c;而因为 FTP协议的简单、易用、广泛支持等优点&#xff0c;让很多企业早期都普遍使用&#xff0c;随着数量量的增多&#xff0c;和对安全的要求越来越高&#xff0c;FTP也暴露出了一些列问题&#xff0c;小编…

C++构建简单静态库实例(cmakelist)

一、开发实例 通过cmake构建静态开发实例如下: 1.1 代码目录 代码目录结构如下: 1.2 代码内容 1.2.1 CMakeLists.txt # CMake 最低版本要求 cmake_minimum_required(VERSION 3.10)# 项目名称 project(mylib)# 添加源文件 set(SOURCE_FILESsrc/mylib

谷歌Linux内核自动测试平台架构介绍-用自动测试测试难以测试的问题

1 摘要 内核和硬件等低级系统已被证明极难进行有效测试&#xff0c;因此&#xff0c;许多内核测试都是以手动为主方式进行的。现有的大多数测试框架都是为测试与底层平台隔离的高级软件而设计的&#xff0c;而底层平台被假定是稳定可靠的。测试底层平台本身需要一套全新的假设…

互联网大厂面试题目

阿里篇 1.1.1 如何实现一个高效的单向链表逆序输出&#xff1f; 1.1.2 已知sqrt(2)约等于1.414&#xff0c;要求不用数学库&#xff0c;求sqrt(2)精确到小数点后10位 1.1.3 给定一个二叉搜索树(BST)&#xff0c;找到树中第 K 小的节点 1.1.4 LRU缓存机制 1.1.5 关于epoll和…

table表格中使用el-popover 无效问题解决

实例只针对单个的按钮管用在表格里每一列都有el-popover相当于是v-for遍历了 所以我们在触发按钮的时候并不是单个的触发某一个 主要执行 代码 <el-popover placement"left" :ref"popover-${scope.$index}"> 动态绑定了ref 关闭弹窗 执行deltask…

mysql的索引原理

目录 一、索引采用B树的优势二、为什么不使用其他数据结构2.1、哈希索引2.2平衡二叉树B树 参考 mysql索引采用B树 一、索引采用B树的优势 1可以进行范围查找&#xff0c;通过单向链表解决&#xff08;通过单向链表已经排好序&#xff09;。 2非叶子结点只存储key&#xff0c;不…

【Java EE初阶三 】线程的状态与安全(下)

3. 线程安全 线程安全&#xff1a;某个代码&#xff0c;不管它是单个线程执行&#xff0c;还是多个线程执行&#xff0c;都不会产生bug&#xff0c;这个情况就成为“线程安全”。 线程不安全&#xff1a;某个代码&#xff0c;它单个线程执行&#xff0c;不会产生bug&#xff0c…

Git:常用命令(二)

查看提交历史 1 git log 撤消操作 任何时候&#xff0c;你都有可能需要撤消刚才所做的某些操作。接下来&#xff0c;我们会介绍一些基本的撤消操作相关的命令。请注意&#xff0c;有些操作并不总是可以撤消的&#xff0c;所以请务必谨慎小心&#xff0c;一旦失误&#xff0c…

地下城游戏(dp问题)

1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 从下往上填&#xff0c;每一行&#xff0c;每一行从右往左 5.返回值 dp[0][0]

OpenCV-Python(21):OPenCV查找及绘制轮廓

1.认识轮廓 1.1 目标 理解什么是轮廓学习掌握找轮廓、绘制轮廓等学习使用cv2.findContours()、cv2.drawContours()函数的用法 1.2 什么是轮廓 在OpenCV中&#xff0c;轮廓是图像中连续的边界线的曲线&#xff0c;具有相同的颜色或者灰度&#xff0c;用于表示物体的形状。轮廓…

docker 在线安装mysql 8.0.21版本

1、拉取mysql 8.0.21版本镜像 2、查看镜像 docker images 3、在宿主机 /usr/local/mysql 下的 conf 文件夹下&#xff0c;创建 my.cnf 文件&#xff0c;并编辑内容 [mysql] default-character-setutf8 [client] port3306 default-character-setutf8 [mysqld] port3306 se…

前后台分离开发

前后台分离开发 简介 前后台分离开发&#xff0c;就是在项目开发过程中&#xff0c;对于前端代码的开发由专门的前端开发人员负责&#xff0c;后端代码则由后端开发人员负责&#xff0c;这样可以做到分工明确、各司其职&#xff0c;提高开发效率&#xff0c;前后端代码并行开…

20231231_小米音箱接入GPT

参考资料&#xff1a; GitHub - yihong0618/xiaogpt: Play ChatGPT and other LLM with Xiaomi AI Speaker *.设置运行脚本权限 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned *.配置小米音箱 ()pip install miservice_fork -i https://pypi.tuna.tsinghua.edu.cn/sim…

单机+内部备份_全备案例

此场景为单机数据库节点内部备份&#xff0c;方便部署和操作&#xff0c;但备份REPO与数据库实例处于同一个物理主机&#xff0c;冗余度较低。 前期准备 配置ksql免密登录(必须) 在Kingbase数据库运行维护中&#xff0c;经常用到ksql工具登录数据库&#xff0c;本地免密登录…

Kafka安装及简单使用介绍

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

电子邮件地址填写指南:格式与常见问题解答

一个专业的电子邮件地址是一个你只用于工作目的的通信帐户。当你给收件人发送电子邮件时&#xff0c;这是他们最先看到的细节之一。无论你的职位或行业如何&#xff0c;拥有一个专业的电子邮件地址都可以提高你和所在公司的可信度。 在本文中我们解释了专业的电子邮件地址是什么…

Reac03:react脚手架配置(代理配置)

react脚手架配置 reactAjax下载Axios配置代理第二种配置代理的方式 github搜索案例 reactAjax React本身只关注于界面&#xff0c;并不包含发送ajax请求的代码前端应用需要通过ajax请求与后台进行交互(json数据)react应用中需要集成第三方ajax(或自己封装) 常用的ajax请求库 j…

STL——queue容器

1.queue基本概念 概念&#xff1a;queue是一种先进先出&#xff08;First In First Out,FIFO&#xff09;的数据结构&#xff0c;它有两个出口。 队列容器允许从一端新增元素&#xff0c;从另一端移除元素。 队列中只有队头和队尾才可以被外界使用&#xff0c;因此队列不允许…

mongoose中http server服务器解决“Access-Control-Allow-Origin mongoose”跨域问题

问题 使用mongoose做http服务器&#xff0c;自己构造的浏览器端jquery在访问server时&#xff0c;会遇到&#xff1a; Access to XMLHttpRequest at http://127.0.0.1:8000/ from origin null has been blocked by CORS policy: No Access-Control-Allow-Origin header is pr…

关于HTTPS

目录 什么是加密 对称加密 非对称加密 中间人攻击 引入证书 HTTPS是一个应用层的协议,是在HTTP协议的基础上引入了一个加密层. HTTP协议内容都是按照文本的方式明文传输,这就导致在传输的过程中出现一些被篡改的情况. 运营商劫持事件 未被劫持的效果,点击下载按钮,就会…