【设计模式】学习笔记(二)——创建型设计模式
目录
- 一、创建型设计模式介绍
- 二、工厂模式
- 2.1 简单工厂
- 2.2 工厂方法
- 2.3 抽象工厂
- 2.4 扩展:万能工厂
- 三、单例模式
- 3.1 单例模式介绍
- 3.2 单例模式实现
- 3.2.1 饿汉模式
- 3.2.2 懒汉模式
- 3.2.3 懒汉和懒汉的区别
- 四、原型模式
- 4.1 原型模式介绍
- 4.2 原型模式实现
- 五、创建者模式
- 5.1 创建者模式介绍
- 5.2 创建者模式实现
- 5.3 创建者模式扩展
- 5.4 工厂方法模式VS建造者模式
- 5.5 抽象工厂模式VS建造者模式
一、创建型设计模式介绍
概念:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
分类:
- 工厂模式(Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
二、工厂模式
概念:它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
- 共有三种不同的工厂模式:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
而三种不同的工厂模式也描述不同的应用场景
静态常量
2)懒汉模式
- 线程不安全
- 线程安全
- 同步方法块
- 双重检查
- 静态内部类
- 枚举
标红为推荐使用的方式
3.2.1 饿汉模式
饿汉模式是指在类的加载的时候就创建一个实例。饿汉模式又分两种实现方式:
1)通过静态常量实现
优点:写法比较简单,就是在类转载的时候就完成实例化。避免了线程同步问题。
缺点:是在类转载的时候就完成实例化,如果从头到尾未使用过这个实例,则会照成内存的浪费。
结论:这种单例模式可用,可能造成资源浪费,所以一般在确定一定会用到这个类时采用。
代码示例
步骤如下:
1)构造器私有化(防止 new)
2)类的内部创建对象
3)向外暴露一个静态的公共方法。getInstance
class Singleton{
//1.构造器私有化,外部不能new
private Singleton(){
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,放回实例对象
public static Singleton getInstance(){
return instance;
}
}
2)通过静态代码块实现。
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也就是类转载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面一样
结论:这种单例模式可用,但是会造成资源浪费
代码示例
1)构造器私有化(防止 new)
2)类的内部声名静态对象
3)在静态代码块中,创建单例对象
4)提供一个公有的静态方法,放回实例对象
class Singleton{
private static Singleton instance;
private Singleton(){
}
static {// 在静态代码块中,创建单例对象
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
3.2.2 懒汉模式
懒汉模式是指在调用方法的时候进行实例化的工作。实现方式有六种:
1)线程不安全
优点:起到了懒加载的效果,但是只能在单线程下使用。
缺点:如果在多线程下,一个线程进入了if(singletion == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
结论:在实际开发中,不要使用这种方式(多线程环境下,线程不安全)
代码示例
实现步骤
1)构造器私有化(防止 new)
2)类的内部声名静态对象
3)提供一个静态的公有方法,当使用到该方法时,才去创建 instance
class Singleton{
private static Singleton instance;
private Singleton(){
}
//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
2)线程安全,同步方法
优点:解决了线程不安全问题
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想要获得该类实例,直接return就行了。
结论:在实际开发中,不推荐使用这种方法(方法进行同步效率太低)
代码示例
实现步骤
1)构造器私有化(防止 new)
2)类的内部声名静态对象
3)
class Singleton{
private static Singleton instance;
private Singleton(){
}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
3)线程安全,同步代码块
这种方法,本意是对上一种实现方法的改进,因为前面方法效率太低,改为同步产生实例化的代码块。但这种同步方法并不能起到线程同步的作用。跟第一种实现方法情形一致,假如一个线程进入了if(singletion == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
结论:在实际开发中,不能使用这种方式
代码示例
实现步骤
1)构造器私有化(防止 new)
2)类的内部声名静态对象
3)提供一个静态的公有方法,在代码块中加入同步处理的代码(synchronized)
class Singleton{
private static Singleton singleton;
private Singleton(){
}
//提供一个静态的公有方法,在代码块中加入同步处理的代码(synchronized)
//即懒汉式
public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
4)双重检查
优点:线程安全,延迟加载,效率较高
缺点:无!!!
结论:在实际开发中,推荐使用这种单例设计模式
代码示例
实现步骤
1)构造器私有化(防止 new)
2)类的内部声名静态对象并用volatile(用来确保将变量的更新操作通知到其他线程)关键字修饰
3)提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
class Singleton{
private static volatile Singleton instance;
private Singleton(){
}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
public static synchronized Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){//当第一个线程执行完对象实例化操作后
if(instance == null) {//后面的线程来到这里时判断
instance = new Singleton();
}
//else 到下面返回 instance
}
}
//而当再后面的线程来时在最上方的判断语句中就直接来到这里返回instance
return instance;
}
}
5)静态内部类
优点:
- 线程安全(在类进行初始化时,别的线程是无法进来的。)
- 延迟加载(外部类的装载不会导致静态内部类的装载)
- 效率高
缺点:无 !!!
结论:实际开发中,推荐使用这种单例设计模式
代码示例
实现步骤
1)构造器私有化(防止 new)
2)类的内部声名静态对象
3)写一个静态内部类,该类中有一个静态属性外部类的静态常量
4)提供一个静态的公有方法,直接返回静态内部类常量属性
class Singleton{
private static Singleton instance;
private Singleton(){
}
//写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
public static synchronized Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
6)枚举
这是借助JDK1.5中添加的枚举来实现单例模式。
优点:保证了线程安全、防止了反序列化重新创建新的对象
缺点:无 !!!
结论:推荐使用
代码示例
//使用枚举,可以实现单例,推荐
enum Singleton{
INSTANCE;//属性
public void sayOK(){
System.out.println("ok~");
}
}
3.2.3 懒汉和饿汉的区别
| 区别 | 饿汉 | 懒汉 |
|---|---|---|
| 线程 | 安全 | 不安全(不过有解决方案) |
| 资源加载 | 占据一定的内存相应的在调用时速度也会更快 | 第一次掉用时要初始化如果要做的工作比较多性能上会有些延迟 |
四、原型模式
4.1 原型模式简介
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
结构:
- 抽象原型类:规定了具体原型对象必须实现的 clone() 方法。
- 具体原型类:实现抽象原型类的 clone() 方法,它是被复刻的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
适用场景:
- 对象的创建非常复制,可以适用原型模式快捷的创建对象。
- 性能和安全要求比较高。
4.2 原型模式实现
原型模式的克隆分为浅克隆和深克隆
浅克隆:只克隆原对象中的基本类型数据,非基本数据类型数据的指向的还是原堆中地址
深克隆:将对象完全克隆,属性中引用的其他对象不再指向原有对象地址
Java中的Object类中提供了 clone() 方法来实现浅克隆。Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable 接口的子类实现就是具体的原型类。
浅克隆
实现步骤:
// 1.需要克隆的类实现 Cloneable 接口
// 2.重新Clone方法
public class Realizetype implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//测试一下
public static void main(String[] arge) throws CloneNotSupportedException {
Realizetype realizetyp = new Realizetype();
Realizetype clone = (Realizetype) realizetyp.clone();
}
}
深克隆
实现深克隆的方法有很多种,这里我们使用的是序列化+反序列化来实现深克隆
import java.io.*;
public class Realizetype implements Cloneable{
public static void main(String[] arge) throws Exception {
//创建原型对象
Student student = new Student();
//创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:/xxx.txt"));
//写对象
oos.writeObject(student);
//释放资源
oos.close();
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:/xxx.txt"));
//读取对象
Student student1 = (Student) ois.readObject();
//释放资源
oos.close();
}
}
//实现序列号接口
class Student implements Serializable {
}
五、创建者模式
5.1 创建者模式介绍
概念:将一个复杂对象的构建与分离,使得同样的构建过程可以创建不同的表示。
结构:
-
抽象创建者(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
-
具体创建者(ConcreteBuilder):实现 Burider 接口,完成复杂产品的各个部分的具体创建方法。在构建过程后,提供产品的实例。
-
产品类(Product):要创建的复杂对象
-
指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
优点:
- 封装性好 : 创建 和 使用 分离 ;
- 扩展性好 : 建造类之间 相互独立 , 在 一定程度上解耦 ;
缺点:
- 增加类数量 : 产生多余的 Builder 对象 ;
- 内部修改困难 : 如果 产品内部发生变化 , 建造者也要相应修改 ;
适用场景:
- 结构复杂 : 对象 有 非常复杂的内部结构 , 有很多属性 ;
- 分离创建和使用 : 想把 复杂对象 的 创建 和 使用 分离 ;
- 当创造一个对象 需要很多步骤时 , 适合使用建造者模式 ;
- 当创造一个对象 只需要一个简单的方法就可以完成 , 适合使用工厂模式
5.2 创建者模式实现
假设要创建一个双层牛肉鸡腿堡??,这个肉(meat)和配料(dosing)非常复杂,采用创建者方式。
点击查看代码
/**
* 汉堡类
*/
public class Hamburger {
// 配料部件,这里使用字符串作为部件,真正使用是传入对象作为部件
private String meat;//肉
private String dosing;//配料
public Hamburger(String meat, String dosing) {
this.meat = meat;
this.dosing = dosing;
}
public Hamburger() {
}
public String getMeat() {
return meat;
}
public void setMeat(String meat) {
this.meat = meat;
}
public String getDosing() {
return dosing;
}
public void setDosing(String dosing) {
this.dosing = dosing;
}
@Override
public String toString() {
return "Hamburger{" +
"meat='" + meat + '\'' +
", dosing='" + dosing + '\'' +
'}';
}
public static class Beefburger {
}
}
/**
* 抽象创建者
*/
public abstract class Builder {
// 将hamburger对象创建在抽象创建者中,实现类就可以对这个类进行创建部件
Hamburger hamburger = new Hamburger();
abstract void setmeat();
abstract void setdosing();
abstract Hamburger createHamburger();
}
/**
* 牛肉堡创造者类
*/
public class Beefburger extends Builder{
@Override
void setbeef() {
hamburger.setMeat("牛肉");
}
@Override
void setdosing() {
hamburger.setDosing("烧烤酱");
}
@Override
Hamburger createHamburger() {
return hamburger;
}
}
/**
* 鸡腿堡创造者类
*/
public class Chickenburger extends Builder{
@Override
void setbeef() {
hamburger.setMeat("鸡肉");
}
@Override
void setdosing() {
hamburger.setDosing("沙拉酱");
}
@Override
Hamburger createHamburger() {
return hamburger;
}
}
/**
* 指挥者类
*/
public class Director {
private Builder builder;
// 初始化创建者
public Director(Builder builder){
this.builder = builder;
}
// 构建方法,装配部件
public Hamburger construct(){
builder.setbeef();
builder.setdosing();
Hamburger hamburger = builder.createHamburger();
return hamburger;
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
// 鸡腿堡创建
Chickenburger chickenburger = new Chickenburger();
Director director = new Director(chickenburger);
Hamburger hamburger = director.construct();
// 牛肉堡创建
Beefburger beefburger = new Beefburger();
Director director1 = new Director(beefburger);
Hamburger hamburger1 = director1.construct();
System.out.println(hamburger);
System.out.println(hamburger1);
}
}
/*测试结果
Hamburger{meat='鸡肉', dosing='沙拉酱'}
Hamburger{meat='牛肉', dosing='烧烤酱'}*/
可以看出,指挥者类对于产品的创建有着至关重要的作用,Builder只是提供了部件的创建,而真正决定怎么创建的,需要由指挥者类(Director)来决定
当部件比较少的时候,可以将指挥者类合并到抽象创建者类里面,但这样就违背了设计原则中的单一职责原则
/**
* 抽象创建者
*/
public abstract class Builder {
// 将hamburger对象创建在抽象创建者中,实现类就可以对这个类进行创建部件
Hamburger hamburger = new Hamburger();
abstract void setmeat();
abstract void setdosing();
abstract Hamburger createHamburger();
public Hamburger construct(){
this.setmeat();
this.setdosing();
Hamburger hamburger = this.createHamburger();
return hamburger;
}
}
5.3 创建者模式扩展
有时候我们还会遇到这样的问题
//当我们对象属性定义过多时,我们可以使用创建者模式进行链式赋值
//增加其代码的可读性。操作简单,重构前:
public class Hamburger {
private String meat;
private String dosing;
private String vegetable;
private String bread;
public String getMeat() {
return meat;
}
public void setMeat(String meat) {
this.meat = meat;
}
public String getDosing() {
return dosing;
}
public void setDosing(String dosing) {
this.dosing = dosing;
}
public String getBread() {
return bread;
}
public void setBread(String bread) {
this.bread = bread;
}
public String getVegetable() {
return vegetable;
}
public void setVegetable(String vegetable) {
this.vegetable = vegetable;
}
public Hamburger(String meat, String dosing, String vegetable, String bread) {
this.meat = meat;
this.dosing = dosing;
this.vegetable = vegetable;
this.bread = bread;
}
@Override
public String toString() {
return "Hamburger{" +
"meat='" + meat + '\'' +
", dosing='" + dosing + '\'' +
", vegetable='" + vegetable + '\'' +
", bread='" + bread + '\'' +
'}';
}
}
/**
* 测试类
*/
public class Client{
public static void main(String[] args) {
Hamburger hamburger=new Hamburger("牛肉","烧烤酱","青菜","普通面包");
System.out.println(hamburger);
}
}
//测试结果
//Hamburger{meat='牛肉', dosing='烧烤酱', vegetable='青菜', bread='普通面包'}
重构后
/**
* 静态内部创建者类改进后
*/
public class Hamburger {
private String meat;
private String dosing;
private String vegetable;
private String bread;
// 将构造方法私有化,采用创建者实现构建
private Hamburger(Builder builder){
this.meat = builder.meat;
this.dosing = builder.dosing;
this.vegetable = builder.vegetable;
this.bread = builder.bread;
}
// 创建一个静态内部创建者类
public static class Builder{
private String meat;
private String dosing;
private String vegetable;
private String bread;
// 分别创建属性
public Builder setMeat(String val) {
meat = val;
return this;
}
public Builder setDosing(String val) {
dosing = val;
return this;
}
public Builder setVegetable(String val) {
vegetable = val;
return this;
}
public Builder setBread(String val) {
bread = val;
return this;
}
// 外部类的构造方式已经私有了,所以通过内部类方法返回一个Phone对象
public Hamburger build(){
// 内部类可以直接调用外部类的私有构造器
// 再将内部类这个对象传给私有构造器,让他来进行构造
return new Hamburger(this);
}
}
@Override
public String toString() {
return "Builder{" +
"meat='" + meat + '\'' +
", dosing='" + dosing + '\'' +
", vegetable='" + vegetable + '\'' +
", bread='" + bread + '\'' +
'}';
}
}
通过改进后,我们就可以通过调用静态内部类的方法,来实现构造器方法
由于内部类每设置一个属性就返回 Builder 所有可以连接赋值
并且还可以选择性的构造,不易造成错误
5.4 工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
5.5 抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
此文章只作为学习过程记录!!!