创建型模式关注点是如何创建对象,其核心思想是要把对象的创建和使用相分离,这样使得两者能相对独立地变换。
单例模式
一个类只允许创建一个对象,那这个类就是一个单例类,这种设计模式就叫作单例模式。这个概念理解起来比较简单,单例模式主要用来表示全局唯一类,例如配置信息类。在系统中,应该只有一个配置文件,当配置文件被加载进入内存之中以后,也应该只有一个配置类的对象,这种情况下就可以使用单例模式进行设计。
单例的Java实现
饿汉式
1 2 3 4 5 6 7
| public class Instance{ private static Instance instance=new Instance(); public static Instance getInstance(){ return instance; } }
|
饿汉式的实现方式比较简单,在类加载的时候就已经创建并初始化好了instance实例对象,不过这一实现方式的缺点是不能够实现延迟加载。
双重检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Instance{ private volatile static Instance instance; public static Instance getInstance(){ if (instance==null){ synchronized (Instance.class){ if (instance==null){ instance=new Instance(); } } } return instance; } }
|
在这种实现方式中,在instance被创建之前,会进入synchronized锁逻辑之中,只要instance被创建之后,就再也不会进入到加锁逻辑之中了。同时因为指令重排序的问题,可能会导致Instance对象被new出来,并且赋值给instance之后,还没来得及初始化,就被使用了,需要给instance成员变量添加上volatile关键字,禁止指令重排序。
静态内部类
1 2 3 4 5 6 7 8 9
| public class Instance{ private static class InstanceHolder{ private static final Instance instance=new Instance(); } public static Instance getInstance(){ return InstanceHolder.instance; } }
|
利用Java的静态内部类也可以实现单例模式,InstanceHolder是一个静态内部类,当外部类Instance被加载的时候不会创建InstanceHolder对象,只有当调用getInstance时才会被加载创建instance。这种方式既保证了线程安全又做到了延迟加载。
枚举
1 2 3 4
| public enum Instance{ INSTANCE }
|
枚举是实现最简单的实现方法,通过枚举自身的特性,保证了线程安全性以及实例的唯一性。
工厂模式
工厂模式也是比较常见的一种设计模式,一般分为三种:简单工厂、工厂方法、抽象工厂。
简单工厂
下面这段代码模拟的是根据配置文件的不同的后缀,获取到不同的解析器,解析存储在文件中的配置信息,这用的就是简单工厂模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| object ParserFactory { fun createParser(fileExtension: String) = when (fileExtension.toLowerCase()) { "xml" -> XmlParser() "json" -> JsonParser() "properties" -> PropertiesParser() else -> throw IllegalArgumentException("未知的类型") } }
abstract class Parser { abstract fun parse(path: String) }
class XmlParser : Parser() { override fun parse(path: String) = TODO("省略逻辑") }
class JsonParser : Parser() { override fun parse(path: String) = TODO("省略逻辑") }
class PropertiesParser : Parser() { override fun parse(path: String) = TODO("省略逻辑") } fun main() { val parser = ParserFactory.createParser("xml") parser.parse("test.xml") }
|
工厂方法
如果为了更好地扩展性,可以使用工厂方法模式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| interface IParseFactory { companion object { private val map = mapOf( "xml" to XmlParserFactory(), "json" to JsonParserFactory(), "properties" to PropertiesParserFactory() ) fun createParser(fileExtension: String) = map[fileExtension.toLowerCase()] ?: throw IllegalArgumentException("未知的类型") } fun createParser(): Parser }
class XmlParserFactory : IParseFactory { override fun createParser() = XmlParser() }
class JsonParserFactory : IParseFactory { override fun createParser() = JsonParser() }
class PropertiesParserFactory : IParseFactory { override fun createParser() = PropertiesParser() }
abstract class Parser { abstract fun parse(path: String) }
class XmlParser : Parser() { override fun parse(path: String) = TODO("省略逻辑") }
class JsonParser : Parser() { override fun parse(path: String) = TODO("省略逻辑") }
class PropertiesParser : Parser() { override fun parse(path: String) = TODO("省略逻辑") }
fun main() { val parserFactory = IParseFactory.createParser("xml") val parser = parserFactory.createParser() parser.parse("test.xml") }
|
工厂方法需要额外创造诸多的Factory类,会增加代码的复杂性。每个Factory类只是做简单的new操作,没有必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适。但是如果当对象的创建逻辑比较复杂,需要很多操作时,则可以将复杂的创建逻辑添加到多个工厂类之中,让每个工厂类不过于复杂。而如果使用简单工厂模式,将所有的创建逻辑放到工厂类之中,会导致这个工厂类十分复杂。
抽象工厂
在简单工厂和工厂方法之中,类只有一个分类方式。但是如果有两种分类方式,比如不仅按照文件格式来分类(例如Json,Xml,Properties),还可以通过解析的对象来分类(例如规则配置,系统配置,业务配置等等),就会有3*3个类,会让系统难以维护。抽象工厂就是为了这一场景而诞生的。
抽象工厂可以让一个工厂负责创建多个不同类型的对象,而不是之创建一种对象,可以有效减少工厂类的个数,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface IParseFactory { companion object { private val map = mapOf( "xml" to XmlParserFactory(), "json" to JsonParserFactory(), "properties" to PropertiesParserFactory() ) fun createParser(fileExtension: String) = map[fileExtension.toLowerCase()] ?: throw IllegalArgumentException("未知的类型") } fun createParser1(): Parser fun createParser2(): Parser }
class XmlParserFactory : IParseFactory { override fun createParser1() = XmlParser1() override fun createParser2() = XmlParser2() }
|
创建者模式
最常见创建一个对象的方法是调用类的构造函数来完成,但是在有些情况下这种方式就不适用了,就需要使用创建者模式来创建对象。例如对于一个线程池配置类,有以下几个成员变量
成员变量 |
解释 |
是否必填 |
默认值 |
name |
名称 |
是 |
|
maxPoolSize |
最大连接数 |
否 |
10 |
corePoolSize |
线程池大小 |
否 |
5 |
keepAliveTime |
存活时间 |
否 |
1min |
如果使用构造函数,则代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class ThreadPoolConfig { public static void main(String[] args) { var config = new ThreadPoolConfig("test", 10, null, null); }
private String name; private int maxPoolSize = 10; private int corePoolSize = 5; private int keepAliveTime = 1;
public ThreadPoolConfig(String name, Integer maxPoolSize, Integer corePoolSize, Integer keepAliveTime) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("名称不能为空"); } this.name = name; if (maxPoolSize != null) { if (maxPoolSize <= 0) { throw new IllegalArgumentException("maxPoolSize 必须为正数"); } this.maxPoolSize = maxPoolSize; } if (corePoolSize != null) { if (corePoolSize < 0) { throw new IllegalArgumentException("corePoolSize 必须为正数"); } this.corePoolSize = corePoolSize; } if (keepAliveTime != null) { if (keepAliveTime < 0) { throw new IllegalArgumentException("keepAliveTime 必须为正数"); } this.keepAliveTime = keepAliveTime; } } }
|
如果按照上面的代码,那么随着可配置项的增多,构造函数的列表会变得很长,容易搞错传递参数的顺序。为了解决这一个问题,就需要使用到建造者模式。在建造者模式中,先通过set方法设置建造者的变量值,所有逻辑检验的部分被放到了build方法之中。使用建造者模式重新实现的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public class ThreadPoolConfig { public static void main(String[] args) { var config = new ThreadPoolConfig.Builder() .setName("test") .setKeepAliveTime(100).build(); }
private String name; private int maxPoolSize; private int corePoolSize; private int keepAliveTime;
private ThreadPoolConfig(Builder builder) { this.name = builder.name; this.maxPoolSize = builder.maxPoolSize; this.corePoolSize = builder.corePoolSize; this.keepAliveTime = builder.keepAliveTime; }
public static class Builder { private String name; private int maxPoolSize = 10; private int corePoolSize = 5; private int keepAliveTime = 1;
public ThreadPoolConfig build() { if (name == null || name.isBlank()) { throw new IllegalArgumentException("名称不能为空"); } if (maxPoolSize <= 0) { throw new IllegalArgumentException("maxPoolSize 必须为正数"); } if (corePoolSize < 0) { throw new IllegalArgumentException("corePoolSize 必须为正数"); } if (keepAliveTime < 0) { throw new IllegalArgumentException("keepAliveTime 必须为正数"); } return new ThreadPoolConfig(this); }
public Builder setName(String name) { this.name = name; return this; }
public Builder setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; return this; }
public Builder setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; return this; }
public Builder setKeepAliveTime(int keepAliveTime) { this.keepAliveTime = keepAliveTime; return this; } } }
|
原型模式
原型模式指的是在创建新对象的时候,通过现有的一个原型来创建。原型模式的使用如下所示。
1 2 3 4 5 6 7 8 9 10 11 12
| data class User( val id: Int, val name: String, val permission: MutableList<String> )
fun main() { val permissionList = arrayListOf("user") val user1 = User(1, "test", permissionList) val user2 = user1.copy() }
|
深拷贝与浅拷贝
原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝和深拷贝的区别在于,浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址。而深拷贝不仅仅会递归的赋值本身,得到完全独立的对象。kotlin的copy函数在进行拷贝的时候默认使用的是浅拷贝,在上面的代码运行完成之后,向user1中permission的列表添加admin属性,可以看到在user2的permission也出现了admin。
1 2 3 4 5 6 7
|
fun main() { user1.permission.add("admin") println(user2.permission) }
|
为了解决这个问题,可以在copy调用的时候将一个新的permission列表传递给user2,这样就可以做到深拷贝。
1 2 3 4 5 6 7
| fun main() { val user2 = user1.copy(permission = user1.permission.toMutableList()) user1.permission.add("admin") println(user2.permission) }
|
或者也可以通过先序列化后反序列的方式进行深拷贝。例如在javascript中先通过JSON.stringify来进行序列化,后通过JSON.parse来反序列化进行深拷贝。
1 2 3 4 5 6 7
| var user1 = { id:1, name:"test", permission:["admin"] } var user2 = JSON.parse(JSON.stringify(user1))
|