当前位置:网站首页>创建者模式大汇总
创建者模式大汇总
2022-06-22 17:58:00 【OldZhangYH】
创建者模式大汇总
源代码地址
单例模式
单例模式确保了在整个系统中这个类只有一个对象被创建。他提供了一种访问他唯一的一个对象的方式,可以直接访问,不需要额外实例化对象。
单例模式有几个显著的特点:
- 私有化构造方法,这样就不能通过构造方法来创建对象了。
- 对外提供静态方法来让外接获取对象。
- 私有化静态变量,来保证全局只有一个变量。
分类
单例模式分为两类:
- 饿汉式:类加载的时候对象就会被创建
- 懒汉式:类加载时对象不会被创建,首次使用的时候类才会被创建。
饿汉式的几种实现方式
类加载的时候就生成了对象,但是不用的话就很浪费内存.
静态变量方法
public class HungrySingleton1 {
private HungrySingleton1(){}
private static HungrySingleton1 instance=new HungrySingleton1();
public static HungrySingleton1 getInstance(){
return instance;
}
}
静态代码块方法
public class HungrySingleton2 {
private HungrySingleton2(){}
private static HungrySingleton2 instance;
static{
instance=new HungrySingleton2();
}
public static HungrySingleton2 getInstance(){
return instance;
}
}
上述的这两种方式其实差不多,就是在类里面new了一个私有静态变量,然后对外的方法只返回他。
使用枚举类实现单例模式
public enum HungrySingleton3 {
INSTANCE;
public void method1(){
System.out.println("do sth");
}
}
怎么样是不是非常的简单,而且枚举类的实现方式是最安全的。不会被反射或者序列化破解。
懒汉式的几种实现方式
懒汉式在类被使用到的时候,才会生成对象避免了内存的浪费。
线程不安全的懒汉式
这个方式就是在调用getInstance()方法的时候会先判断是否已经创建了对象。如果创建了就返回,没有就创建一个在返回。但是在多线程的情况下,有可能出二次创建的现象。
public class Lazy1 {
private Lazy1() {
}
private static Lazy1 instance;
public static Lazy1 getInstance() {
if (instance == null) {
instance=new Lazy1();
}
return instance;
}
}
线程安全的懒汉方
和上面的差异就是多了一个synchronized关键字。使用synchronized关键字给getInstance()方法加锁,但是锁的加在了整个方法上,但是性能较差。
public class Lazy1 {
private Lazy1() {
}
private static Lazy1 instance;
public static synchronized Lazy1 getInstance() {
if (instance == null) {
instance=new Lazy1();
}
return instance;
}
}
双重检查模式
双重检查锁方式,把锁加在了方法里面而不是整个方法。只有在需要创建对象的时候才需要获得锁,让锁的粒度更细了。效率也就更高。
public class Lazy2 {
private Lazy2(){}
//volatile 是为了保证指令的有序性,不然在多线程环境下 由于jvm的指令重排序可能会导致空指针
private static volatile Lazy2 instance;
public static Lazy2 getInstance(){
if(instance==null){
synchronized(Lazy2.class){
if(instance==null){
instance=new Lazy2();
}
}
}
return instance;
}
}
静态内部类模式
通过在对象里面创建一个静态内部类来持有对象。静态内部类在类加载是不会被加载,只有在被调用的时候才会被加载,从而实现了懒汉式。而且是线程安全的。
public class Lazy3 {
private Lazy3(){}
private static class Lazy3Holder{
private static final Lazy3 INSTANCE=new Lazy3();
}
public static Lazy3 getInstance(){
return Lazy3Holder.INSTANCE;
}
}
问题
序列化、反序列化破坏单例模式
先说一下基本的概念,
- 序列化:把对象转换成字节流,然后写入到文件或者通过网络传输给其他终端。
- 反序列化:把字节流重新转化为对象。
因此我们把类序列化成字节流,然后保存到文件中.就能做到创建对象。
public static void write2File() throws FileNotFoundException, IOException {
Lazy2 l = Lazy2.getInstance();
ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oStream.writeObject(l);
oStream.close();
}
然后通过字节流在反序列化成对象。
public static Lazy2 read4File() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream oInputStream=new ObjectInputStream(new FileInputStream("obj.txt"));
Lazy2 l=(Lazy2)oInputStream.readObject();
oInputStream.close();
return l;
}
我们直接反序列化两次,生成两个对象。输出结果为:false。说明我们生成了两个不同的对象,破坏了单例模式全局唯一对象的特性。
public static void main(String[] args) throws Exception{
write2File();
System.out.println(read4File()==read4File());
}
反射破解单例模式
反射可以通过一个类对应的class类来获得这个类所有的成员变量和方法,不管是私有的还是公开的。
可以理解为,每个类都有一个大门。门里面是他的私有的变量和方法,只有类自己有钥匙打开大门去操作他们。但是反射不讲武德,他有电锯。直接把门锯开了,然后去操作这个类私有的变量和方法。
public class Reflection {
public static void main(String[] args) throws Exception{
//获取Lazy2的class类
Class<Lazy2> class1=Lazy2.class;
//获取私有构造方法
Constructor con=class1.getDeclaredConstructor();
//暴力破门(设置私有方法可访问)
con.setAccessible(true);
//使用构造方法创建对象
Lazy2 l=(Lazy2) con.newInstance();
Lazy2 l2=(Lazy2) con.newInstance();
System.out.println(l==l2);
}
}
输出:false
解决方式
一下的例子我都是在Lazy2上修改的
序列化、反序列化
在需要序列化反序列化的单例模式类中添加readResolve()方法来规定反序列化时的返回值。
在反序列化时,Java会判断是否有这个函数,如果有就返回这个函数的返回值。没有就会new一个新的对象来返回
public Object readResolve(){
return instance;
}
public static void main(String[] args) throws Exception{
write2File();
Lazy2 l=read4File();
Lazy2 l2=read4File();
System.out.println(l==l2);
}
输出:true
反射
既然反射时通过调用构造方法来实现的创建对象的,那么我们就在构造方法里面加上一点逻辑判断,在有对象的情况下报错,不让他创建就好了。
private Lazy2(){
synchronized(Lazy2.class){
if(instance!=null){
throw new RuntimeErrorException(null, "不准创建多个对象");
}
}
}
在常规调用之后,在使用反射创建对象就会报错。
Lazy2 l=Lazy2.getInstance();
Lazy2 l1=(Lazy2) con.newInstance();
但是直接使用反射还是可以破解
Lazy2 l1=(Lazy2) con.newInstance();
Lazy2 l2=(Lazy2) con.newInstance();
反射实在太猛了
枚举大法好!
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的。完美符合单例模式!
简单工厂模式
使用一个接口A来规定工厂生产的产品的的规范。然后使用这个接口A的实现类来说明实际生产的产品。
举例
我们的程序需要输出日志
public class Log {
public void log(String name){
if (name.equals("txt")) {
System.out.println("往txt中输出数据");
} else if (name.equals("cmd")) {
System.out.println("往CMD中输出数据");
}
}
}
但是一般情况下,我们会有很多种Log类,如果每个Log类都可以往txt和命令行中输出日志。假设有n种Log类,那么这段代码我们就要写n次,这问题还不大。但是突然有一天,你需要往一数据库里面写日志了,那我们就需要修改上面的代码,改成这样。
public class Log {
public void log(String name){
if (name.equals("txt")) {
System.out.println("往txt中输出数据");
} else if (name.equals("cmd")) {
System.out.println("往CMD中输出数据");
} else if(name.equals("db")){
System.out.println("往数据库种输出日志");
}
}
}
改一次还简单,但是别忘了我们有n个Log类,这就要了老命了。我们要去n个地方改,而且忘记改了就会导致系统出错。
因此我们可以使用简单工厂模式。
- 抽象出一个
Log产品
我们定义一个Log接口来规范Log的功能。
public interface Log{
public void writeLog();
}
- 写实现类
CMDLog,TxtLog来表示具体Log的类(具体的产品)
public class CMDLog implements Log{
@Override
public void writeLog() {
System.out.println("往CMD中输出数据");
}
}
public class TxtLog implements Log{
@Override
public void writeLog() {
System.out.println("往txt中输出数据");
}
}
- 定义一个
LogFactory工厂来返回Log产品
public class LogFactory {
public static Log createLog(String name) {
if (name.equals("txt")) {
return new TxtLog();
} else if (name.equals("cmd")) {
return new CMDLog();
} else {
return null;
}
}
}
- 不同的
Log调用工厂来获得Log实现类,进行业务操作即可
public class ApacheLog {
public void log(String name){
Log log=LogFactory.createLog(name);
log.writeLog();
}
public void ApacheLogMethod(){
System.out.println("Apache日志专属方法");
}
}
public class SysLog {
public void log(String name){
Log log=LogFactory.createLog(name);
log.writeLog();
}
public void sysLogMethod(){
System.out.println("系统日志专属方法");
}
}
SysLog和ApacheLog都有他们独有的方法,但是又可以随意的往任何地方输出日志,虽然最后还是用了if/else来判断具体往哪里输出日志,但是当我们需要增加新的需求时,我们只需要修改LogFactory的代码和新增一个实现类就好了。不需要去修改SysLog和ApacheLog了。
测试
public static void main(String[] args) {
ApacheLog apacheLog=new ApacheLog();
SysLog sysLog=new SysLog();
apacheLog.log("txt");
apacheLog.ApacheLogMethod();
sysLog.log("cmd");
sysLog.sysLogMethod();
}
输出:
往txt中输出数据
Apache日志专属方法
往CMD中输出数据
系统日志专属方法
优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度。
使用场景:
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
注意事项
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
工厂方法模式
相对于简单工厂模式,工厂方法模式完全符合了开闭原则(对修改关闭,对扩展开放)
概念
工厂方法模式定义一个工厂接口来用于创建对象,然后让其子类来决定具体创建哪个对象。工厂方法让一个产品类的创建延迟到了其工厂的子类中。
结构
- 抽象工厂:提供创建产品的接口,通过它来访问具体工厂并创建产品
- 具体工厂:抽象工厂的实现子类,实现了抽象工厂的方法,完成产品的创建
- 抽象产品:定义了产品规范,描述了产品的功能和特性
- 具体产品:抽象产品的实现子类,由具体工厂创建和具体工厂一一对应。
类图

优缺点
优点:
- 在系统增加新的产品的时候,只需要添加具体的产品类和对应的具体工厂,不用修改原代码。满足了开闭原则
- 用户只要直到具体工厂的名字就能得到产品,无需关注产品具体的创建过程。
缺点:
- 每次加新产品就会多两个类,长此以往类会非常多,增加系统的复杂度
抽象工厂模式(AbstractFactory)
概念
上述的简单工厂模式和工厂方法模式都只是生产同一种类的产品。比如咖啡工厂只生产咖啡,牧场只养动物,九阳豆浆机只造豆浆机,手机店只卖手机。但是在现实情况下,店家不会这么单纯滴。就比如华为手机店,就不可能只卖手机,他还买电脑、电视甚至是汽车(对的,华为还卖汽车哦)。
也就是说简单工厂模式和工厂方法模式都只能生产同一类的产品(不同的咖啡,或者不同品种的狗狗),而抽象工厂可以生产同一族,但是不同类的产品(同时生产手机和电脑,同时生产上衣和裤子)
关于同类和同族,举例来说就是所有电子产品都是同一族的,比如电脑和手机是同一族的。然后一个产品的不同类型是同一类的,比如华为mate20和华为p30。
具体结构
结构其实和工厂方法是一样的,但是有一些细微的区别
- 抽象工厂:包含了多个创建产品的方法,可以创建不同类的产品(工厂方法只能创建一类)
- 具体工厂:抽象工厂的实现类
- 抽象产品:定义了产品的规范(可以认为同一个接口定义的产品就是同一类)
- 具体产品:抽象产品的实现类
类图

优缺点
- 优点:一个产品族中的产品只需要一个类就行了。当一个产品族中多个对象(手机和电脑)被设计成一起工作时(一起被摆在华为的店里面卖),他能保证客户端只使用用一个产品族的对象(顾客就在华为店里面只能买到华为的,买不到苹果的)。
- 缺点:当一个产品族需要增加一个新产品时(抽象接口里面多了个新方法,就像华为突然开始卖车),所有的工厂类都需要修改。(所有实现类都得实现这个方法,都得加上生产车的方法)
使用场景
由于抽象工厂在使用的时候会有非常多的类,所以不是什么时候都适合用他的。(比如我这咖啡店就只卖咖啡)
- 当被创建的对象时同一个产品族的(华为店里面的手机、电脑、耳机)
- 系统里面有很多个产品族,但是用户一般情况下使用其中一族(比如一般用户都是用苹果全家桶或者华为全家桶)
原型模式
他使用一个已经被创建的实例作为模板,通过复制来创建一个和原型对象相同的新对象。
结构
- 抽象原型类:规定了具体原型对象必须实现
clone()方法。(Cloneable) - 具体原型类:实现抽象原型类的clone()方法,他是可被复制的对象
- 访问类:使用clone()方法来复制。
类图及实现
原型模式的复制方式有两种:
浅拷贝:直接复制了对象A地址给另一个对象B。由于对象A,B指向的是同一个地址因此修改B,A也会跟着改变。
深拷贝:复制了对象A的值给另一个对象B,这两个对象没有任何关系指向不同的地址。修改其中一个并不会对另一个造成影响。
接下来的例子我都按浅拷贝来实现了,有关浅拷贝和深拷贝的详细内容,看这里
使用场景
- 对象的创建很复杂,就可以直接复制来快捷的获得对象
- 性能和安全的要求比较高
建造者模式
- 建造者模式将一个复杂对象的构建和表示分离,让同样额构建过程可以表示不同的产品。
- 他分离了产品部分的构造和装配。分别有
Builder和Director负责,并最终可以生成复杂的对象。这个模式适用于:生成一个及其复杂的对象的情况。 - 由于构造和装配的解耦,因此相同的构造,不同的装配可以生成不同的对象。不同的构造,相同的装配,不同的构造也可以生成不同的对象。(可以理解为做汉堡。构造就是汉堡的肉是什么肉,菜是什么菜,面包是什么面包。装配就是放的顺序,可以肉夹面包也可以面包夹肉)
- 建造者模式将产品和组装的过程分开。用户只需要指定复杂对象的类型就可以获得该对象,无需直到具体的对象的创建细节
结构
- 抽象建造者(
Builder):规定建造者需要实现的方法,不涉及具体的对象部件的创建 - 具体建造者:抽象建造者的实现类,实现了其中的方法,负责具体对象部件的创建
- 产品:要被创建的对象
- 指挥者(
Director):调用具体建造者来创建复杂对象的各个部件,并按照顺序把他们拼起来。
案例类图

优缺点
优点:
- 建造者模式的封装性很好,可以有效的应对产品部件的变化,而且业务逻辑集中在
Director中稳定性较好。 - 客户端不需要知道产品的创建细节,将产品本身和产品的创建过程解耦,使相同的创建过程可以创建不同的产品对象。
- 可以精细的控制产品的创建过程。
- 符合开闭原则,很容易扩展。想要有个新的产品只要多搞一个
Builder的实现类也就是具体建造者就行了。
缺点: - 产品更新迭代需要加一个新部件的话,就需要修改一大堆具体建造者类。
- 如果产品直接的差异性过大就不能用建造者模式
使用场景
建造者模式通常用来创建复杂对象。这种对象的部件变化频繁,但是组合的过程相对稳定。
- 对象复杂,由多个部件组成。但是部件之间的建造顺序稳定
- 产品的构建过程和最终的表示是独立的
相似创建者模式的对比
工厂方法模式和建造者模式
工厂方法模式注重整体对象的创建,而建造者模式注重部件构造的过程,通过一个个部件的搭建来最终生成一个复杂对象。
也就是说工厂方法模式生成电脑就是直接生成一台电脑,建造者模式就是先拿到cpu、gpu然后主板什么的最终拼成一台电脑。
抽象工厂模式和建造者模式
抽象工厂模式关注的是一个产品族的生产。他不关心构建的过程,只关心什么产品由什么工厂生产即可。
建造者则是按照指定的蓝图构建产品,他的目的是通过组装零件来生产一个新产品。
如果抽象工厂模式是汽车配件生产厂,那建造者就是汽车组装厂。由抽象工厂生产零件,再由建造者组装。
边栏推荐
- IPLOOK作为O-RAN联盟会员,将共同促进5G产业发展
- shell脚本详解(四)——循环语句之while循环和until循环(附加例题及解析)
- 5GC和卫星融合通信方案
- SRE必将走向混沌工程时代--华为云混沌工程实践
- Detailed explanation of shell script (x) -- how to use sed editor
- Flutter系列-Dart基础语法学习
- IPLOOK和思博伦通信建立长期合作
- 机械设备行业数字化供应链集采平台解决方案:优化资源配置,实现降本增效
- UE4_ Ue5 make 3dui follow the camera orientation (attached works)
- 預訓練語言模型,bert,RoFormer-Sim又稱SimBERTv2
猜你喜欢

2022 operation of simulated examination platform for examination question bank of welder (elementary) special operation certificate

数字赋能机械制造业,供应链协同管理系统解决方案助力企业供应链再升级

20billion vs 5billion, how much is the "dehydration" little red book worth?

shell脚本详解(二)——条件测试、if语句和case分支语句

在循环中动态改变标签元素的样式
Redis usage scenario sharing (project practice)

Several important viewpoints on operation and maintenance, monitoring and aiops

Detailed explanation of session mechanism and related applications of session

TypeScript(7)泛型

3GPP 5G R17标准冻结,RedCap作为重要特性值得关注!
随机推荐
[suggestions collection] common usage scenarios of message queue
Iplook, as a member of o-ran alliance, will jointly promote the development of 5g industry
Flutter系列-搭建Flutter开发环境
chrome突然无法复制粘贴了
5g short message solution
Typescript (7) generic
回文数(简单版)
wpa_supplicant的状态机迁移
Error in created hook: “TypeError: Cannot read property ‘tableId‘ of undefined“
Programmer's tool encyclopedia [continuous update]
std::enable_shared_from_this 错误:error: expected template-name before ‘<’ token
org. apache. ibatis. binding. BindingException: Invalid bound statement (not found)
如何提高工作效率?苹果电脑效率工具合集
贪心之区间问题(4)
wpa_cli参数说明
5G 短消息解决方案
有效的括号
SRE必将走向混沌工程时代--华为云混沌工程实践
What happened to this page when sqlserver was saving
数字赋能机械制造业,供应链协同管理系统解决方案助力企业供应链再升级