目录
模式一:单例模式
一、简介
二、种类
1. 饿汉式(静态常量)
1.1. 代码
1.2. 优缺点
2. 饿汉式(静态代码块)
2.1. 代码
2.2. 优缺点
3. 懒汉式(线程不安全)
3.1. 代码
3.2. 优缺点
4. 懒汉式(线程安全,同步方法)
4.1. 代码
4.2. 优缺点
5. 懒汉式(线程安全,同步代码块)
5.1. 代码
5.2. 优缺点
6. 双重检查(推荐使用)
6.1. 代码
6.2. 优缺点
7. 静态内部类(推荐使用)
7.1. 代码
7.2. 优缺点
8. 枚举(推荐使用)
8.1. 背景
8.2. 简单实现
8.3. 优缺点
9. 单例模式在 JDK 应用的源码分析
10. 单例模式注意事项和细节说明
三、应用
1. Runtime(JDK源码应用)
四、总结
模式二:工厂模式
一、案例一:(披萨问题)
需求场景
传统方式
1. 简单工厂模式
1.1. 简介
1.2. 实现
2. 工厂方法模式
2.1. 场景
2.2. 简介
2.3. 实现
3. 抽象工厂模式
3.1. 基本介绍
3.2. 实现
二、应用
1. Calendar(JDK 源码应用)
1.1 代码
1.2 总结
三、总结
模式三:原型模式
一、案例一:克隆羊问题
1. 克隆羊问题
2. 传统方式
2.1. 简介
2.2. 优缺点
3. 原型模式
3.1. 简介
3.2. UML 类图
3.3. 代码实现
3.4. 应用 Android中的Intent
3.5. 浅拷贝
3.6. 深拷贝
二、总结
模式四:建造者模式
一、案例一:建房子问题
1. 盖房项目需求
2. 传统方式
3. 建造者模式
3.1. 简介
3.2. 四个角色
3.3. 代码实现
二、应用
1. StringBuilder(JDK源码应用)
2. AlertDialog(安卓应用)
三、总结
模式一:单例模式
一、简介
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且
该类只提供一个取得其对象实例的方法(静态方法)。
比如 Hibernate 的SessionFactory,它充当数据存储源的代理,并负责创建 Session 对象。SessionFactory 并
不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式。
单例模式有八种方式:
- 饿汉式(静态常量) ✅
- 饿汉式(静态代码块) ✅
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查 ✅
- 静态内部类 ✅
- 枚举 ✅
二、种类
1. 饿汉式(静态常量)
1.1. 代码
public class SingleTonEH {public static void main(String[] args) {//测试SingleTon instance = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance==instance2);System.out.println(instance.hashCode());System.out.println(instance2.hashCode());}
}//饿汉式(静态常量)
class SingleTon{//1. 构造器私有化,外部不能newprivate SingleTon(){}//2.本类内部创建对象实例private final static SingleTon instance = new SingleTon();//3. 提供一个公有的静态方法,返回实例对象public static SingleTon getInstance() {return instance;}
}
1.2. 优缺点
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
- 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
- 结论:这种单例模式可用,可能造成内存浪费
2. 饿汉式(静态代码块)
2.1. 代码
//饿汉式(静态代码块)
class Singleton2 {//1. 构造器私有化, 外部不能newprivate Singleton2() {}//2.本类内部创建对象实例private static Singleton2 instance;// 3. 在静态代码块中,创建单例对象static {instance = new Singleton2();}//4. 提供一个公有的静态方法,返回实例对象public static Singleton2 getInstance() {return instance;}
}
2.2. 优缺点
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
- 结论:这种单例模式可用,但是可能造成内存浪费
3. 懒汉式(线程不安全)
3.1. 代码
// 懒汉式(线程不安全)
class SingleTon3{private static SingleTon3 instance;private SingleTon3(){}public static SingleTon3 getInstance(){if (instance==null){instance = new SingleTon3();}return instance;}
}
3.2. 优缺点
- 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
- 结论:在实际开发中,不要使用这种方式.
4. 懒汉式(线程安全,同步方法)
4.1. 代码
// 懒汉式(线程安全,同步方法)
class SingleTon4{private static SingleTon4 instance;private SingleTon4(){}//提供一个静态的公有方法,synchronized 加入同步处理的代码,解决线程安全问题public static synchronized SingleTon4 getInstance(){if (instance==null){instance = new SingleTon4();}return instance;}
}
4.2. 优缺点
- 解决了线程安全问题
- 效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低
- 结论:在实际开发中,不推荐使用这种方式
5. 懒汉式(线程安全,同步代码块)
5.1. 代码
不能使用
// 懒汉式(线程安全,同步代码块)
class SingleTon5{private static SingleTon5 instance;private SingleTon5(){}public static synchronized SingleTon5 getInstance(){if (instance==null){synchronized (SingleTon5.class){instance = new SingleTon5();}}return instance;}
}
5.2. 优缺点
- 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
- 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一 致,假如一个线程进入了if(singleton ==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
- 结论:在实际开发中,不能使用这种方式
6. 双重检查(推荐使用)
6.1. 代码
//双重检查
class SingleTon6{private static volatile SingleTon6 instance;private SingleTon6(){}//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题public static SingleTon6 getInstance(){if (instance==null){synchronized (SingleTon6.class){if (instance==null) {instance = new SingleTon6();}}}return instance;}
}
6.2. 优缺点
- Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这样就可以保证线程安全了。
- 这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton == null),直接 return 实例化对象,也避免的反复进行方法同步.
- 线程安全;延迟加载;效率较高
- 结论:在实际开发中,推荐使用这种单例设计模式
7. 静态内部类(推荐使用)
7.1. 代码
// 静态内部类完成, 推荐使用
class Singleton7 {//Singleton单例private Singleton(){}//构造器私有化private static class SingletonInstance{//写一个静态内部类,该类中有一个静态属性private static final Singleton7 INSTANCE = new Singleton7();}//提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCEpublic static Singleton7 getInstance() {return SingletonInstance.INSTANCE;}
}
7.2. 优缺点
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论:推荐使用.
8. 枚举(推荐使用)
8.1. 背景
首先来分析一下克隆、反射和反序列化对单例模式的破坏。
在其他创建型设计模式的学习中,我们已经了解,除了直接通过new和使用工厂来创建对象以外,
还可以通过克隆、反射和反序列化等方式来创建对象。
但是用这些方式来创建对象时有可能会导致单例对象的不唯一,如何解决这些问题呢?
(1) 为了防止客户端使用克隆方法来创建对象,单例类不能实现Cloneable接口,即不能支持clone()方法。
(2) 由于反射可以获取到类的构造函数,包括私有构造函数,因此反射可以生成新的对象。【如何解决:采用枚举实现】
采用一些传统的实现方法都不能避免客户端通过反射来创建新对象,此时,我们可以通过枚举单例对象的方式来解决该问题。
(3) 在原型模式中,我们可以通过反序列化实现深克隆,反序列化也会生成新的对象。
具体来说就是每调用一次readObject()方法,都将会返回一个新建的实例对象,
这个新建的实例对象不同于类在初始化时创建的实例对象。
那么,如何防止反序列化创建对象呢?解决方法一是类不能实现Serializable接口,即不允许该类支持序列化,
这将导致类的应用受限制(有时候我们还是需要对一个对象进行持久化处理);解决方法二就是本文将要详细介绍的枚举实现。
8.2. 简单实现
下面我们分析如何使用枚举Enum来实现单例模式。
Google 首席 Java 架构师、《Effective Java》一书作者、Java集合框架的开创者Joshua Bloch在 Effective
Java 一书中提到:
在这种实现方式中,既可以避免多线程同步问题;还可以防止通过反射和反序列化来重新创建新的对象。
在很多优秀的开源代码中,我们经常可以看到使用枚举方式来实现的单例类。
下面我们来详细分析如何使用枚举实现单例模式。
枚举是在JDK1.5以及以后版本中增加的一个“语法糖”,它主要用于维护一些实例对象固定的类。
例如一年有四个季节,就可以将季节定义为一个枚举类型,然后在其中定义春、夏、秋、冬四个季节的枚举类型的实例对象。
按照Java语言的命名规范,通常,枚举的实例对象全部采用大写字母定义,这一点与Java里面的常量是相同的。
首先我们来看一下最简单的单例模式枚举实现。
因为Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。
最简单的实现方式如下代码所示:
public enum SingleTon8{INSTANCE;public void businessMethod() {System.out.println("我是一个单例!");}
}
大家可以看到,我们定义了一个枚举类型SingleTon8,
在其中定义了一个枚举变量INSTANCE,同时还提供了业务方 businessMethod()。
接下来我们看一下客户端代码,如下所示:
public class MainClass {public static void main(String[] args) {SingleTon8 s1 = SingleTon8.INSTANCE;SingleTon8 s2 = SingleTon8.INSTANCE;System.out.println(s1==s2);}
}
在main()函数中,我们通过Singleton.INSTANCE获得两个对象s1和s2,然后比较s1是否等于s2,
最后输出true,说明s1和s2是同一个对象,所得到的对象具有唯一性。
8.3. 优缺点
- 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式(实现非常简单)
- 结论:推荐使用
9. 单例模式在 JDK 应用的源码分析
- 在 JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
- 代码分析+Debug 源码+代码说明
public class Runtime {private static Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}private Runtime() {}
}
10. 单例模式注意事项和细节说明
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new 的方式
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
三、应用
1. Runtime(JDK源码应用)
- 在 JDK 中,java.lang.Runtime 就是经典的单例模式(饿汉式)
- 代码分析+Debug 源码+代码说明
public class Runtime {private static Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}private Runtime() {}
}
四、总结
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new 的方式
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
模式二:工厂模式
一、案例一:(披萨问题)
需求场景
看一个披萨的项目:要便于披萨种类的扩展,要便于维护
- 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
- 披萨的制作有 prepare,bake, cut, box
- 完成披萨店订购功能
传统方式
抽象Pizza
public abstract class Pizza {protected String name;//名字public abstract void prepare();public void bake(){System.out.println(name+"baking");};public void cut(){System.out.println(name+"cutting");};public void box(){System.out.println(name+"boxing");};public void setName(String name) {this.name = name;}
}
希腊披萨
public class GreekPizza extends Pizza {@Overridepublic void prepare() {System.out.println("希腊披萨准备原材料");}
}
奶酪披萨
public class CheesePizza extends Pizza {@Overridepublic void prepare() {System.out.println("奶酪披萨准备原材料");}
}
披萨订购
public class OrderPizza {public OrderPizza(){Pizza pizza = null;String orderType;//订购披萨的类型do {orderType = getType();if (orderType.equals("greek")){pizza = new GreekPizza();pizza.setName("希腊披萨");}else if (orderType.equals("cheese")){pizza = new CheesePizza();pizza.setName("奶酪披萨");}else {break;}//输出pizza制作过程pizza.prepare();pizza.bake();pizza.cut();pizza.box();}while (true);}//获取客户希望订购的披萨种类private String getType(){try {BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));System.out.println("input pizza 种类");String str = strin.readLine();return str;}catch (IOException e) {e. printStackTrace() ;return "" ;}}
}
客户端
public class PizzaStore {public static void main(String[] args) {OrderPizza orderPizza = new OrderPizza();}
}
类关系图
传统的方式的优缺点
- 优点是比较好理解,简单易操作。
- 缺点是违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
- 比如我们这时要新增加一个 Pizza 的种类(Pepper 披萨),我们需要做如下修改. 如果我们增加一个 Pizza 类,只要是订购 Pizza 的代码都需要修改.
OrderPizza.java改动如下
else if (orderType.equals("pepper")){pizza = new PepperPizza();pizza.setName("胡椒披萨");}
- 改进的思路分析 分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza 的代码,就意味着,也需要修改,而创建 Pizza 的代码,往往有多处。 思路:把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改该类就可,其它有创建到 Pizza 对象的代码就不需要修改了.-> 简单工厂模式
1. 简单工厂模式
1.1. 简介
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
- 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
- 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.
1.2. 实现
- 简单工厂模式的设计方案: 定义一个可以实例化 Pizaa 对象的类,封装创建对象的代码。
public class SimpleFactory {//根据 orderType 返回对应的 Pizza 对象public Pizza createPizza(String orderType){Pizza pizza = null;System.out.println("使用简单工厂模式"); if (orderType.equals("greek")) {pizza = new GreekPizza();pizza.setName(" 希腊披萨 ");} else if (orderType.equals("cheese")) { pizza = new CheesePizza();pizza.setName(" 奶酪披萨 ");} else if (orderType.equals("pepper")) { pizza = new PepperPizza();pizza.setName("胡椒披萨");}return pizza;}//简单工厂模式 也叫 静态工厂模式public static Pizza createPizza2(String orderType){Pizza pizza = null;System.out.println("使用简单工厂模式"); if (orderType.equals("greek")) {pizza = new GreekPizza();pizza.setName(" 希腊披萨 ");} else if (orderType.equals("cheese")) { pizza = new CheesePizza();pizza.setName(" 奶酪披萨 ");} else if (orderType.equals("pepper")) { pizza = new PepperPizza();pizza.setName("胡椒披萨");}return pizza;}}
修改OrderPizza
public class OrderPizza {//简单工厂方式SimpleFactory simpleFactory;Pizza pizza = null;public OrderPizza(SimpleFactory simpleFactory){setFactory(simpleFactory);}//静态工厂创建public OrderPizza(){String orderType = "";//用户输入do {orderType = getType();pizza = SimpleFactory.createPizza2(orderType);//输出pizza制作过程if (pizza!=null){pizza.prepare();pizza.bake();pizza.cut();pizza.box();}else {System.out.println("订购披萨失败");break;}}while (true);}public void setFactory(SimpleFactory simpleFactory) {String orderType = "";//用户输入this.simpleFactory = simpleFactory;do {orderType = getType();pizza = simpleFactory.createPizza(orderType);//输出pizza制作过程if (pizza!=null){pizza.prepare();pizza.bake();pizza.cut();pizza.box();}else {System.out.println("订购披萨失败");break;}}while (true);}
}
修改PizzaStore
public class PizzaStore {public static void main(String[] args) {//传统方式
// new OrderPizza();//使用简单工厂模式new OrderPizza(new SimpleFactory());//静态工厂方式//new OrderPizza();System.out.println("退出程序");}
}
2. 工厂方法模式
2.1. 场景
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者
是伦敦的奶酪 pizza、伦敦的胡椒 pizza。
思路 1
使用简单工厂模式,创建不同的简单工厂类,比如 BJPizzaSimpleFactory、LDPizzaSimpleFactory 等等.
从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好
思路 2
使用工厂方法模式
2.2. 简介
- 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
2.3. 实现
Pizza
public abstract class Pizza {protected String name;//名字public abstract void prepare();public void bake(){System.out.println(name+" baking");};public void cut(){System.out.println(name+" cutting");};public void box(){System.out.println(name+" boxing");};public void setName(String name) {this.name = name;}
}public class BJCheesePizza extends Pizza {@Overridepublic void prepare() {setName("北京奶酪披萨");System.out.println("北京奶酪披萨准备原材料");}
}public class BJPepperPizza extends Pizza {@Overridepublic void prepare() {setName("北京胡椒披萨");System.out.println("北京椒披萨准备原材料");}
}public class LDCheesePizza extends Pizza {@Overridepublic void prepare() {setName("伦敦奶酪披萨");System.out.println("伦敦奶酪披萨准备原材料");}
}public class LDPepperPizza extends Pizza {@Overridepublic void prepare() {setName("伦敦胡椒披萨");System.out.println("伦敦椒披萨准备原材料");}
}
OrderPizza
public abstract class OrderPizza {abstract Pizza createPizza(String orderType);public OrderPizza(){Pizza pizza = null;String orderType;do {orderType = getType();pizza = createPizza(orderType);//抽象方法,由工厂子类完成//输出pizza制作过程if (pizza!=null){pizza.prepare();pizza.bake();pizza.cut();pizza.box();}else {System.out.println("订购披萨失败");break;}}while (true);}//获取客户希望订购的披萨种类private String getType(){try {BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));System.out.println("input pizza 种类");String str = strin.readLine();return str;}catch (IOException e) {e. printStackTrace() ;return "" ;}}}
LDOrderPizza, BJOrderPizza
public class LDOrderPizza extends OrderPizza {@OverridePizza createPizza(String orderType) {Pizza pizza = null;if (orderType.equals("cheese")){pizza = new LDCheesePizza();}else if (orderType.equals("pepper")){pizza = new LDPepperPizza();}return pizza;}
}public class BJOrderPizza extends OrderPizza {@OverridePizza createPizza(String orderType) {Pizza pizza = null;if (orderType.equals("cheese")){pizza = new BJCheesePizza();}else if (orderType.equals("pepper")){pizza = new BJPepperPizza();}return pizza;}
}
PizzaStore
public class PizzaStore {public static void main(String[] args) {new BJOrderPizza();new LDOrderPizza();}
}
3. 抽象工厂模式
3.1. 基本介绍
- 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
3.2. 实现
Factory
public interface AbsFactory {//由工厂子类具体实现public Pizza createPizza(String orderType);
}public class BJFactory implements AbsFactory {@Overridepublic Pizza createPizza(String orderType) {Pizza pizza = null;if (orderType.equals("cheese")){pizza = new BJCheesePizza();}else if (orderType.equals("pepper")){pizza = new BJPepperPizza();}return pizza;}
}public class LDFactory implements AbsFactory {@Overridepublic Pizza createPizza(String orderType) {Pizza pizza = null;if (orderType.equals("cheese")){pizza = new LDCheesePizza();}else if (orderType.equals("pepper")){pizza = new LDPepperPizza();}return pizza;}
}
OrderPizza
public class OrderPizza {AbsFactory factory;public OrderPizza(AbsFactory factory){setFactory(factory);}public void setFactory(AbsFactory absFactory) {this.factory = absFactory;Pizza pizza = null;String orderType = "";do {orderType = getType();//factory 可能是北京/伦敦的工厂子类pizza = factory.createPizza(orderType);//输出pizza制作过程if (pizza!=null){pizza.prepare();pizza.bake();pizza.cut();pizza.box();}else {System.out.println("订购披萨失败");break;}}while (true);}//获取客户希望订购的披萨种类private String getType(){try {BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));System.out.println("input pizza 种类");String str = strin.readLine();return str;}catch (IOException e) {e. printStackTrace() ;return "" ;}}
}
PizzaStore
public class PizzaStore {public static void main(String[] args) {OrderPizza orderPizza = new OrderPizza(new BJFactory());}
}
二、应用
1. Calendar(JDK 源码应用)
JDK 中的 Calendar 类中,就使用了简单工厂模式
1.1 代码
通过静态单例方式获取Calendar实例
public class FactoryDemo {public static void main(String[] args) {// getInstance 是 Calendar 静态方法Calendar cal = Calendar.getInstance();}
}
Calendar.getInstance();
public static Calendar getInstance(){return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));}
createCalendar(...); 的工厂模式实践
private static Calendar createCalendar(TimeZone zone,Locale aLocale){CalendarProvider provider =LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();if (provider != null) {try {return provider.getInstance(zone, aLocale);} catch (IllegalArgumentException iae) {// fall back to the default instantiation}}Calendar cal = null;if (aLocale.hasExtensions()) {String caltype = aLocale.getUnicodeLocaleType("ca");if (caltype != null) {switch (caltype) {case "buddhist":cal = new BuddhistCalendar(zone, aLocale);break;case "japanese":cal = new JapaneseImperialCalendar(zone, aLocale);break;case "gregory":cal = new GregorianCalendar(zone, aLocale);break;}}}if (cal == null) {if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {cal = new BuddhistCalendar(zone, aLocale);} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"&& aLocale.getCountry() == "JP") {cal = new JapaneseImperialCalendar(zone, aLocale);} else {cal = new GregorianCalendar(zone, aLocale);}}return cal;}
1.2 总结
- 工厂模式的意义 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
- 不要覆盖基类中已经实现的方法。
三、总结
- 工厂模式的意义 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
- 创建对象实例时,不要直接 new 类, 而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用。
- 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
- 不要覆盖基类中已经实现的方法。
模式三:原型模式
一、案例一:克隆羊问题
1. 克隆羊问题
现在有一只羊 tom,姓名为: tom, 年龄为:1,颜色为:白色,
请编写程序创建和 tom 羊 属性完全相同的 10 只羊。
2. 传统方式
2.1. 简介
public class Client {public static void main(String[] args) {Sheep sheep = new Sheep("tom", 1, "白色");Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());}
}public class Sheep {private String name;private int age;private String color;public Sheep(String name, int age, String color) {this.name = name;this.age = age;this.color = color;}public String getName() {return name;}public int getAge() {return age;}public String getColor() {return color;}
}
2.2. 优缺点
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
- 改进的思路分析
思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制一份,但是需要实现
clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力=>原型模式
3. 原型模式
3.1. 简介
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
- 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣
3.2. UML 类图
原型模式原理结构图-UML类图
原理结构图说明
- Prototype : 原型类,声明一个克隆自己的接口
- ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作
- Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
3.3. 代码实现
在Sheep中实现Cloneable接口并重写clone方法
public class Sheep implements Cloneable{...//克隆该实例,使用默认的clone方法来完成@Overrideprotected Object clone() {Sheep sheep = null;try {sheep=(Sheep) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return sheep;}
}
客户端调用
public class Client {public static void main(String[] args) {Sheep sheep = new Sheep("tom", 1, "白色");//传统方式Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());//克隆方式Sheep cloneSheep = (Sheep) sheep.clone();System.out.println(cloneSheep);}
}
3.4. 应用 Android中的Intent
public class Intent implements Parcelable, Cloneable {...@Overridepublic Object clone() {return new Intent(this);}...}
3.5. 浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 前面我们克隆羊就是浅拷贝
- 浅拷贝是使用默认的 clone()方法来实现 sheep = (Sheep) super.clone();
public class Sheep implements Cloneable{public Sheep friend;//
}
浅拷贝对于引用类型,只是将该成员变量的引用值(内存地址)复制一份给新的对象
public class Client {public static void main(String[] args) {Sheep sheep = new Sheep("tom", 1, "白色");sheep.friend = new Sheep("jack",2,"黑色");//传统方式
// Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());//克隆方式Sheep cloneSheep = (Sheep) sheep.clone();Sheep cloneSheep2 = (Sheep) sheep.clone();System.out.println(cloneSheep);System.out.println("cloneSheep.friend == "+ cloneSheep.friend.hashCode() );//1627674070System.out.println("cloneSheep2.friend == "+ cloneSheep2.friend.hashCode() );//1627674070}
}
3.6. 深拷贝
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
- 深拷贝实现方式 1:重写 clone 方法来实现深拷贝
- 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)
需要克隆的引用对象
public class DeepCloneAbleTarget implements Serializable,Cloneable {private static final long serialVersionUID = 1L;private String cloneName;private String cloneClass;//构造器public DeepCloneAbleTarget(String cloneName, String cloneClass) {this.cloneName = cloneName;this.cloneClass = cloneClass;}//因为该类的属性,都是 String , 因此我们这里使用默认的 clone 完成即可@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
对象实体
public class DeepProtoType implements Serializable, Cloneable{public String name; //String 属 性public DeepCloneAbleTarget deepCloneableTarget;// 引用类型public DeepProtoType() {super();}//完成深拷贝 - 方式1 使用clone方法//如果引用类型变量很多,只能逐一单独处理@Overrideprotected Object clone() throws CloneNotSupportedException {Object deep = null;//完成对基本数据类型(属性)和字符串的克隆deep = super.clone();//对引用类型的属性,进行单独处理DeepProtoType deepProtoType = (DeepProtoType) deep;deepProtoType.deepCloneableTarget = (DeepCloneAbleTarget) deepCloneableTarget.clone();return deep;}//深拷贝 - 方式2 通过对象的序列化实现(推荐)public Object deepClone(){//创建流对象ByteArrayOutputStream bos = null;ObjectOutputStream oos = null;ByteArrayInputStream bis = null;ObjectInputStream ois = null;try {//序列化bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);oos.writeObject(this);//当前这个对象以对象流的方式输出//反序列化bis = new ByteArrayInputStream(bos.toByteArray());ois = new ObjectInputStream(bis);DeepProtoType copyObj = (DeepProtoType) ois.readObject();return copyObj;} catch (IOException | ClassNotFoundException e) {e.printStackTrace();return null;}finally {//关闭流try {bos.close();oos.close();bis.close();ois.close();} catch (IOException e) {e.printStackTrace();}}}}
测试代码
public class Client {public static void main(String[] args) throws CloneNotSupportedException {DeepProtoType deepProtoType = new DeepProtoType();deepProtoType.name = "宋江";deepProtoType.deepCloneableTarget = new DeepCloneAbleTarget("大牛","超跑");//方式1 重写 clone 方法实现深拷贝
// DeepProtoType deepProtoType2 = (DeepProtoType) deepProtoType.clone();
// DeepProtoType deepProtoType3 = (DeepProtoType) deepProtoType.clone();//方式2 使用序列化来实现深拷贝DeepProtoType deepProtoType2 = (DeepProtoType) deepProtoType.deepClone();DeepProtoType deepProtoType3 = (DeepProtoType) deepProtoType.deepClone();//DeepProtoType 的 引用类型变量 实现了深拷贝System.out.println("deepProtoType2.deepCloneableTarget="+deepProtoType.hashCode());//1581781576System.out.println("deepProtoType3.deepCloneableTarget="+deepProtoType2.hashCode());//1534030866System.out.println("deepProtoType4.deepCloneableTarget="+deepProtoType3.hashCode());//664223387}
}
二、总结
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
- 在实现深克隆的时候可能需要比较复杂的代码
- 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则,这点请同学们注意.
模式四:建造者模式
一、案例一:建房子问题
1. 盖房项目需求
- 需要建房子:这一过程为打桩、砌墙、封顶
- 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的.
- 请编写程序,完成需求.
2. 传统方式
AbstractHouse
public abstract class AbstractHouse {//打地基abstract void buildBasic();//砌墙abstract void buildWall();//封顶abstract void roofed();public void build(){buildBasic();buildWall();roofed();}
}
CommonHouse
public class CommonHouse extends AbstractHouse{@Overridevoid buildBasic() {System.out.println("普通房子打地基");}@Overridevoid buildWall() {System.out.println("普通房子砌墙");}@Overridevoid roofed() {System.out.println("普通房子封顶");}
}
Client
public class Client {public static void main(String[] args) {CommonHouse commonHouse = new CommonHouse();commonHouse.build();}
}
传统方式的问题分析
- 优点是比较好理解,简单易操作。
- 设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好. 也就是说,这种设计方案,把产品(即:房子) 和 创建产品的过程(即:建房子流程) 封装在一起,耦合性增强了。
- 解决方案:将产品和产品建造过程解耦 => 建造者模式.
3. 建造者模式
3.1. 简介
- 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们, 用户不需要知道内部的具体构建细节。
3.2. 四个角色
- Product(产品角色): 一个具体的产品对象。
- Builder(抽象建造者): 创建一个 Product 对象的各个部件指定的 接口/抽象类。
- ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
- Director(指挥者): 构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
3.3. 代码实现
- 需要建房子:这一过程为打桩、砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程,下面我们使用建造者模式(Builder Pattern)来完成
- 思路分析图解(类图)
House实体
public class House {private String basic;private String wall;private String rooft;...
}
抽象HouseBuilder
public abstract class HouseBuilder {protected House house = new House();//建造流程//打地基abstract void buildBasic();//砌墙abstract void buildWall();//封顶abstract void roofed();//建造房子 将产品返回public House buildHouse(){return house;}
}
普通房子具体构建者 CommonHouseBuilder
public class CommonHouseBuilder extends HouseBuilder {@Overridevoid buildBasic() {house.setBasic("普通房子打地基5米");}@Overridevoid buildWall() {house.setWall("普通房子砌墙24厘米");}@Overridevoid roofed() {house.setRooft("普通房子封顶砖瓦顶");}
}
高楼具体构建者 CommonHouseBuilder
public class HighBuildingHouseBuilder extends HouseBuilder {@Overridevoid buildBasic() {house.setBasic("高楼打地基50米");}@Overridevoid buildWall() {house.setWall("高楼砌墙32厘米");}@Overridevoid roofed() {house.setRooft("高楼封顶透明顶");}
}
指挥者HouseDirector
public class HouseDirector {HouseBuilder houseBuilder = null;//构造器传入houseBuilderpublic HouseDirector(HouseBuilder builder){this.houseBuilder = builder;}//通过setter传入public void setHouseBuilder(HouseBuilder builder) {this.houseBuilder = builder;}//建造房子的流程交给指挥者public House constructHouse(){houseBuilder.buildBasic();houseBuilder.buildWall();houseBuilder.roofed();return houseBuilder.buildHouse();}
}
客户端测试
public class Client {public static void main(String[] args) {//普通房子HouseBuilder commonHouseBuilder = new CommonHouseBuilder();//指挥者使用CommonHouseBuilder盖普通房子HouseDirector director = new HouseDirector(commonHouseBuilder);//改完普通房子 返回产品House commonHouse = director.constructHouse();System.out.println(commonHouse);//高楼HouseBuilder highHouseBuilder = new HighBuildingHouseBuilder();//指挥者使用HighBuildingHouseBuilder盖高楼director = new HouseDirector(highHouseBuilder);House highHouse=director.constructHouse();System.out.println(highHouse);}
}
二、应用
1. StringBuilder(JDK源码应用)
- Appendable 接口定义了多个 append 方法(抽象方法), 即 Appendable 为抽象建造者, 定义了抽象方法
- AbstractStringBuilder 实现了 Appendable 接口方法,这里的 AbstractStringBuilder 已经是建造者,只是不能实例化
- StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由 AbstractStringBuilder 完成 , 而 StringBuilder继承了 AbstractStringBuilder
2. AlertDialog(安卓应用)
AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle("title").setMessage("message").setCancelable(false)....create();alertDialog.show();
自定义AppInfo,使其支持类似的Builder模式
public class AppInfo {private final String name;//必填字段private int versionCode;private String versionName;private String desption;private String author;private String company;private AppInfo(Builder builder) {//通过Builder进行赋值name = builder.name;versionCode = builder.versionCode;versionName = builder.versionName;desption = builder.desption;author = builder.author;company = builder.company;}public String getName() {return name;}public int getVersionCode() {return versionCode;}public String getVersionName() {return versionName;}public String getDesption() {return desption;}public String getAuthor() {return author;}public String getCompany() {return company;}@Overridepublic String toString() {return "AppInfo{" +"name='" + name + '\'' +", versionCode=" + versionCode +", versionName='" + versionName + '\'' +", desption='" + desption + '\'' +", author='" + author + '\'' +", company='" + company + '\'' +'}';}public static final class Builder {private final String name;private int versionCode;private String versionName;private String desption;private String author;private String company;public Builder(String val) {//必填字段通过构造函数进行赋值name = val;}public Builder versionCode(int val) {versionCode = val;return this;}public Builder versionName(String val) {versionName = val;return this;}public Builder desption(String val) {desption = val;return this;}public Builder author(String val) {author = val;return this;}public Builder company(String val) {company = val;return this;}public AppInfo build() {return new AppInfo(this);}}
}
使用
main(...) {AppInfo appInfo = new AppInfo.Builder("builder测试").author("wlei").versionCode(1).versionName("第一版").company("cloverstudio").desption("android中的Builder模式").build();System.out.println(appInfo);}
三、总结
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
- 抽象工厂模式 VS 建造者模式抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品