设计模式 - 创建型模式详解
农夫三拳有点疼~ 人气:1
> 在软件工程中,**创建型模式**是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
>
> 常用创建型模式有:单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式
# 一、单例模式
### 单例模式有以下8种写法:
1. 饿汉式:
1. 静态常量
2. 静态代码块
2. 懒汉式:
1. 线程不安全
2. 线程安全,同步方法
3. 线程安全,同步代码块
3. 双重检查
4. 静态内部类
5. 枚举
### 单例模式的使用场景:
需要频繁创建和销毁的对象;创建时耗时过多或消耗资源过多,但又经常用到的对象(比如session工厂、数据源等)
## 1. 饿汉式 - 静态常量写法
### 代码实现:
```java
/**
* 设计模式之单例模式
* 饿汉式(静态常量)
*/
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println("两次获取的实例一样吗:" + (instance1 == instance2)); //true
}
}
class Singleton {
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton() {
}
//定义为常量,保证实例对象不变
private final static Singleton instance = new Singleton();
//通过此方法获取实例
public static Singleton getInstance() {
return instance;
}
}
```
### 分析:
优点:
- 使用方式简单,在类加载的时候创建实例对象,避免了线程同步问题
缺点:
- 在类加载的时候创建实例对象,但不确定何时使用、是否使用,可能造成内存浪费
## 2. 饿汉式 - 静态代码块写法
### 代码实现:
```java
/**
* 设计模式之单例模式
* 饿汉式(静态代码块写法)
*/
class Singleton{
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton(){
}
//定义为常量,保证实例对象不变
private final static Singleton instance;
static {
instance = new Singleton();
}
//通过此方法获取实例
public static Singleton getInstance(){
return instance;
}
}
```
### 分析:
和静态常量一致,只不过初始化的位置不同,一个在静态代码块,一个直接在常量声明处初始化
## 3. 懒汉式 - 线程不安全
### 代码实现:
```java
/**
* 设计模式之单例模式
* 懒汉式(线程不安全)
*/
class Singleton {
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton() {
}
//声明实例对象
private static Singleton instance;
//通过此方法获取实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
### 分析:
优点:
- 满足随用随拿的特点,解决了内存浪费的问题
缺点:
- 线程不安全,当多个线程访问时,可能创建多个实例,因此实际开发中不可使用
## 4. 懒汉式 - 线程安全 - 同步方法写法
### 代码实现:
```java
/**
* 设计模式之单例模式
* 懒汉式(同步方法)
*/
class Singleton {
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton() {
}
//声明实例对象
private static Singleton instance;
//通过此方法获取实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
### 分析:
虽然解决了线程不安全问题,但锁的范围太大,效率低,开发中尽量不要使用
## 5. 懒汉式 - 线程安全 - 同步代码块写法
### 代码实现:
```java
/**
* 设计模式之单例模式
* 懒汉式(同步代码块写法)
*/
class Singleton {
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton() {
}
//声明实例对象
private static Singleton instance;
//通过此方法获取实例
public static Singleton getInstance() {
if (instance == null) {
//同步代码
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
```
### 分析:
这种方式将同步锁缩小了范围,本意是解决效率问题,但又造成了线程不安全,因此开发中不可使用
## 6. 懒汉式 - 双重检查(推荐使用)
### 代码实现:
```java
/**
* 设计模式之单例模式
* 双重检查
*/
class Singleton {
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton() {
}
//声明实例对象
private static volatile Singleton instance;
//双重判断 + 同步锁
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
### 分析:
既提高了效率也解决了线程安全问题,推荐使用这种方法
## 7. 懒汉式 - 静态内部类(推荐使用)
### 代码实现:
```java
/**
* 设计模式之单例模式
* 静态内部类
*/
class Singleton {
//私有构造方法,使其不可在外部通过构造器实例化
private Singleton() {
}
//静态内部类
private static class SingletonInstance{
private final static Singleton INSTANCE = new Singleton();
}
//获取实例
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
```
### 分析:
利用了类加载机制,保证初始化实例时只有一个线程。Singleton类被装载时并不会被实例化,当调用getInstance方法时才会装载SingletonInstance
## 8. 懒汉式 - 枚举法(推荐使用)
### 代码实现:
```java
/**
* 设计模式之单例模式
* 枚举
*/
enum Singleton{
INSTANCE;
}
```
### 分析:
不仅能规避线程不安全,还能防止反序列化重新创建新的对象
# 二、工厂模式
## 1. 简单工厂模式
### 1.1 介绍
严格来说,简单工厂模式并不是23种常见的设计模式之一,它只算工厂模式的一个特殊实现。简单工厂模式在实际中的应用相对于其他2个工厂模式用的还是相对少得多,因为它只适应很多简单的情况。
简单工厂模式违背了 **开闭原则** (但可以通过反射的机制来避免) 。因为每次你要新添加一个功能,都需要在生switch-case 语句(或者if-else 语句)中去修改代码,添加分支条件。
### 1.2 适用场景
- 需要创建的对象较少
- 客户端不关心对象的创建过程
### 1.3 简单工厂模式角色分配
- **工厂(Factory)角色** :简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
- **抽象产品(Product)角色** :简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
- **具体产品(Concrete Product)角色**:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
### 1.4 简单工厂模式代码实现
新建一个抽象产品 Shape
```java
public interface Shape {
void draw();
}
```
具体产品 Circle、Square,实现 Shape 接口
```java
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("圆形");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("正方形");
}
}
```
工厂 ShapeFactory
```java
public class ShapeFactory {
public static Shape getShape(String name){
if(name == null){
return null;
}
if ("SQUARE".equalsIgnoreCase(name)){
return new Square();
}else if("CIRCLE".equalsIgnoreCase(name)){
return new Circle();
}else{
return null;
}
}
}
```
客户端 Client
```java
public class Client {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("circle");
circle.draw();
Shape square = ShapeFactory.getShape("square");
square.draw();
}
}
```
运行结果
```
圆形
正方形
```
虽然实现了简单工厂模式,但是当我们新增一个需求的时候,需要修改ShapeFactory类的代码,违反了开闭原则,我们可以用反射的方式重写工厂方法
```java
public class ShapeFactory2 {
public static Object getClass(Class<? extends Shape> clazz){
if (clazz == null){
return null;
}
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
```
测试
```java
public class Client2 {
public static void main(String[] args) {
Circle circle = (Circle) ShapeFactory2.getClass(Circle.class);
circle.draw();
Square square = (Square) ShapeFactory2.getClass(Square.class);
square.draw();
}
}
```
运行结果
```java
圆形
正方形
```
## 2. 工厂方法模式
### 2.1 介绍
工厂方法模式应该是在工厂模式家族中是用的最多模式,一般项目中存在最多的就是这个模式。
**工厂方法模式是简单工厂的仅一步深化**, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说 **每个对象都有一个与之对应的工厂** 。
### 2.2 适用场景
- 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
### 2.3 工厂方法模式角色分配
- **抽象工厂(Abstract Factory)角色**:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
- **具体工厂(Concrete Factory)角色** :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
- **抽象产品(Abstract Product)角色** :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
- **具体产品(Concrete Product)角色** :这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应
### 2.4 工厂方法模式代码实现
抽象工厂 Factory
```java
public interface Factory {
Shape getShape();
}
```
具体工厂 CircleFactory、SquareFactory
```java
public class CircleFactory implements Factory {
@Override
public Shape getShape() {
return new Circle();
}
}
public class SquareFactory implements Factory {
@Override
public Shape getShape() {
return new Square();
}
}
```
抽象产品和具体产品继续使用简单工厂模式中的类
客户端
```java
public class Client {
public static void main(String[] args) {
Shape circle = new CircleFactory().getShape();
circle.draw();
Shape square = new SquareFactory().getShape();
square.draw();
}
}
```
运行结果
```java
圆形
正方形
```
## 3. 抽象工厂模式
### 3.1 介绍
在工厂方法模式中,其实我们有一个潜在意识的意识。那就是我们生产的都是同一类产品。抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一种产品,而是可以创建一组产品。
将工厂抽象成两层,Abstract Factory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
### 3.2 适用场景
- 和工厂方法一样客户端不需要知道它所创建的对象的类。
- 需要一组对象共同完成某种功能时,并且可能存在多组对象完成不同功能的情况。(同属于同一个产品族的产品)
- 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)
### 3.3 抽象工厂模式角色分配
- **抽象工厂(Abstract Factory)角色** :是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
- **具体工厂类(Concrete Factory)角色** :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
- **抽象产品(Abstract Product)角色** :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
- **具体产品(Concrete Product)角色** :抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。在抽象工厂中创建的产品属于同一产品族,这不同于工厂模式中的工厂只创建单一产品。
### 3.4 抽象工厂的工厂和工厂方法中的工厂有什么区别?
抽象工厂是生产一整套有产品的(至少要生产两个产品),这些产品必须相互是有关系或有依赖的,而工厂方法中的工厂是生产单一产品的工厂。
### 3.5 抽象工厂模式代码实现
抽象产品:Gun、Bullet
```java
public interface Gun {
void shooting();
}
public interface Bullet {
void loading();
}
```
具体产品:AK47、AK47Bullet、M16、M16Bullet
```java
public class AK47 implements Gun {
@Override
public void shooting() {
System.out.println("AK47射击");
}
}
public class AK47Bullet implements Bullet {
@Override
public void loading() {
System.out.println("AK47装子弹");
}
}
public class M16 implements Gun {
@Override
public void shooting() {
System.out.println("M16射击");
}
}
public class M16Bullet implements Bullet {
@Override
public void loading() {
System.out.println("M16装子弹");
}
}
```
抽象工厂:ArmsFactory
```java
public interface ArmsFactory {
Gun produceGun();
Bullet produceBullet();
}
```
具体工厂:
```java
public class AK47Factory implements ArmsFactory{
@Override
public Gun produceGun() {
return new AK47();
}
@Override
public Bullet produceBullet() {
return new AK47Bullet();
}
}
public class M16Factory implements ArmsFactory{
@Override
public Gun produceGun() {
return new M16();
}
@Override
public Bullet produceBullet() {
return new M16Bullet();
}
}
```
测试
```java
public class Client {
public static void main(String[] args) {
ArmsFactory factory;
factory = new AK47Factory();
Gun ak47 = factory.produceGun();
Bullet ak47Bullet = factory.produceBullet();
ak47Bullet.loading();
ak47.shooting();
factory = new M16Factory();
Gun m16 = factory.produceGun();
Bullet m16Bullet = factory.produceBullet();
m16Bullet.loading();
m16.shooting();
}
}
```
结果
```
AK47装子弹
AK47射击
M16装子弹
M16射击
```
参考:[深入理解工厂模式](https://zhuanlan.zhihu.com/p/37362917)
# 三、原型模式
原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
可以通过重写clone方法实现拷贝,拷贝又分为浅拷贝和深拷贝
## 1. 浅拷贝:
- 对于基本数据类型的成员变量,浅拷贝会直接进行值传递,将该属性值复制一份给新的对象
- 对于引用类型的成员变量,浅拷贝知识将该成员变量的引用值(内存地址)复制一份给新的对象,因此会造成一个对象修改值影响另外一个对象
- 浅拷贝时使用默认的clone()方法来实现,例如:
`Person = (Person)super.clone()`
## 2. 深拷贝:
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象
- 可通过重写clone方法和对象序列化方式(推荐)实现
```java
//使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Company company = null;
try {
company = (Company) super.clone();
//处理引用类型
company.employee = (Employee) employee.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
```
```java
//使用序列化
protected Object deepClone(){
ByteArrayInputStream bis = null;
ByteArrayOutputStream bos = null;
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return ois.readObject();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
```
## 3. 原型模式的分析:
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态的获取对象运行时的状态
- 如果原始对象发生变化(增加或减少属性),其它克隆对象也会发生变化,无需修改代码
- 浅拷贝和深拷贝对引用类型的处理方式不一样
# 四、建造者模式
## 1. 基本介绍
1. 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2. 建造者模式是一步步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构造它们,用户不需要知道内部的具体构建细节。
## 2. 建造者模式的四个角色
- **产品角色(Product)**:一个具体的产品对象
- **抽象建造者(Builder)**:创建一个Product对象的各个部件指定的**接口**或**抽象类**
- **具体建造者(Concrete Builder)**:实现接口,构建和装配各个部件。
- **指挥者(Director)**:构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用:① 隔离了客户与对象的生产过程,②负责控制产品对象的生产过程。
## 3. 原理类图:
![](https://gitee.com/songjilong/FigureBed/raw/master/img/20200319164256.png)
## 4. 建造者模式在 JDK - StringBuilder 中的应用
**在 StringBuilder 继承关系中:**
StringBuilder 继承了 AbstractStringBuilder ,AbstractStringBuilder 实现了 Appendable 接口
**角色分析:**
- 抽象建造者:Appendable
- 具体建造者:AbstractStringBuilder
- 指挥者:StringBuilder
## 5. 建造者模式注意事项与细节
1. 客户端(使用程序)**不必知道产品内部组成的细节**,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2. **每一个具体建造者都相对独立,而与其他的具体建造者无关**,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
3. **可以更加精细地控制产品的创建过程**。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
4. **增加新的具体建造者无须修改原有类库的代码**,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
## 6. 建造者模式代码实现
具体产品:House
```java
public class House {
private String basic;
private String wall;
private String roofed;
//省略getter setter toString()方法
}
```
抽象建造者:HouseBuilder
```java
public abstract class HouseBuilder {
protected House house = new House();
public abstract void buildBasic();
public abstract void buildWall();
public abstract void buildRoof();
public House buildHouse() {
return house;
}
}
```
具体建造者:CommonHouse、HighHouse
```java
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("打10m的地基");
}
@Override
public void buildWall() {
System.out.println("砌20cm的墙");
}
@Override
public void buildRoof() {
System.out.println("封瓦片顶");
}
}
public class HighHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("打20m的地基");
}
@Override
public void buildWall() {
System.out.println("砌50cm的墙");
}
@Override
public void buildRoof() {
System.out.println("封玻璃顶");
}
}
```
指挥者:HouseDirector
```java
public class HouseDirector {
private HouseBuilder builder;
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
public House build(){
builder.buildBasic();
builder.buildWall();
builder.buildRoof();
return builder.buildHouse();
}
}
```
测试:
```java
public class Client {
public static void main(String[] args) {
HouseDirector builderDirector1 = new HouseDirector(new CommonHouse());
builderDirector1.build();
System.out.println("---------");
HouseDirector builderDirector2 = new HouseDirector(new HighHouse());
builderDirector2.build();
}
}
```
结果:
```
打10m的地基
砌20cm的墙
封瓦片顶
---------
打20m的地基
砌50cm的墙
封玻璃顶
```
加载全部内容