在软件工程中, 创建型模式是处理对象创建的设计模式, 试图根据实际情况使用合适的方式创建对象. 基本的对象创建方式可能会导致设计上的问题, 或增加设计的复杂度. 创建型模式通过以某种方式控制对象的创建来解决问题.
常用创建型模式有: 单例模式, 工厂模式, 抽象工厂模式, 原型模式, 建造者模式
一, 单例模式
单例模式有以下 8 种写法:
饿汉式:
静态常量
静态代码块
懒汉式:
线程不安全
线程安全, 同步方法
线程安全, 同步代码块
双重检查
静态内部类
枚举
单例模式的使用场景:
需要频繁创建和销毁的对象; 创建时耗时过多或消耗资源过多, 但又经常用到的对象(比如 session 工厂, 数据源等)
1. 饿汉式 - 静态常量写法
代码实现:
- /**
- * 设计模式之单例模式
- * 饿汉式(静态常量)
- */
- 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. 饿汉式 - 静态代码块写法
代码实现:
- /**
- * 设计模式之单例模式
- * 饿汉式(静态代码块写法)
- */
- class Singleton{
- // 私有构造方法, 使其不可在外部通过构造器实例化
- private Singleton(){
- }
- // 定义为常量, 保证实例对象不变
- private final static Singleton instance;
- static {
- instance = new Singleton();
- }
- // 通过此方法获取实例
- public static Singleton getInstance(){
- return instance;
- }
- }
分析:
和静态常量一致, 只不过初始化的位置不同, 一个在静态代码块, 一个直接在常量声明处初始化
3. 懒汉式 - 线程不安全
代码实现:
- /**
- * 设计模式之单例模式
- * 懒汉式(线程不安全)
- */
- class Singleton {
- // 私有构造方法, 使其不可在外部通过构造器实例化
- private Singleton() {
- }
- // 声明实例对象
- private static Singleton instance;
- // 通过此方法获取实例
- public static Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- }
分析:
优点:
满足随用随拿的特点, 解决了内存浪费的问题
缺点:
线程不安全, 当多个线程访问时, 可能创建多个实例, 因此实际开发中不可使用
4. 懒汉式 - 线程安全 - 同步方法写法
代码实现:
- /**
- * 设计模式之单例模式
- * 懒汉式(同步方法)
- */
- class Singleton {
- // 私有构造方法, 使其不可在外部通过构造器实例化
- private Singleton() {
- }
- // 声明实例对象
- private static Singleton instance;
- // 通过此方法获取实例
- public static synchronized Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- }
分析:
虽然解决了线程不安全问题, 但锁的范围太大, 效率低, 开发中尽量不要使用
5. 懒汉式 - 线程安全 - 同步代码块写法
代码实现:
- /**
- * 设计模式之单例模式
- * 懒汉式(同步代码块写法)
- */
- class Singleton {
- // 私有构造方法, 使其不可在外部通过构造器实例化
- private Singleton() {
- }
- // 声明实例对象
- private static Singleton instance;
- // 通过此方法获取实例
- public static Singleton getInstance() {
- if (instance == null) {
- // 同步代码
- synchronized (Singleton.class) {
- instance = new Singleton();
- }
- }
- return instance;
- }
- }
分析:
这种方式将同步锁缩小了范围, 本意是解决效率问题, 但又造成了线程不安全, 因此开发中不可使用
6. 懒汉式 - 双重检查(推荐使用)
代码实现:
- /**
- * 设计模式之单例模式
- * 双重检查
- */
- 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. 懒汉式 - 静态内部类(推荐使用)
代码实现:
- /**
- * 设计模式之单例模式
- * 静态内部类
- */
- 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. 懒汉式 - 枚举法(推荐使用)
代码实现:
- /**
- * 设计模式之单例模式
- * 枚举
- */
- enum Singleton{
- INSTANCE;
- }
分析:
不仅能规避线程不安全, 还能防止反序列化重新创建新的对象
二, 工厂模式
1. 简单工厂模式
1.1 介绍
严格来说, 简单工厂模式并不是 23 种常见的设计模式之一, 它只算工厂模式的一个特殊实现. 简单工厂模式在实际中的应用相对于其他 2 个工厂模式用的还是相对少得多, 因为它只适应很多简单的情况.
简单工厂模式违背了 开闭原则 (但可以通过反射的机制来避免) . 因为每次你要新添加一个功能, 都需要在生 switch-case 语句 (或者 if-else 语句) 中去修改代码, 添加分支条件.
1.2 适用场景
需要创建的对象较少
客户端不关心对象的创建过程
1.3 简单工厂模式角色分配
工厂 (Factory) 角色 : 简单工厂模式的核心, 它负责实现创建所有实例的内部逻辑. 工厂类可以被外界直接调用, 创建所需的产品对象.
抽象产品 (Product) 角色 : 简单工厂模式所创建的所有对象的父类, 它负责描述所有实例所共有的公共接口.
具体产品 (Concrete Product) 角色: 简单工厂模式的创建目标, 所有创建的对象都是充当这个角色的某个具体类的实例.
1.4 简单工厂模式代码实现
新建一个抽象产品 Shape
- public interface Shape {
- void draw();
- }
具体产品 Circle,Square, 实现 Shape 接口
- 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
- 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
- public class Client {
- public static void main(String[] args) {
- Shape circle = ShapeFactory.getShape("circle");
- circle.draw();
- Shape square = ShapeFactory.getShape("square");
- square.draw();
- }
- }
运行结果
圆形 正方形
虽然实现了简单工厂模式, 但是当我们新增一个需求的时候, 需要修改 ShapeFactory 类的代码, 违反了开闭原则, 我们可以用反射的方式重写工厂方法
- 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;
- }
- }
测试
- 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();
- }
- }
运行结果
圆形 正方形
2. 工厂方法模式
2.1 介绍
工厂方法模式应该是在工厂模式家族中是用的最多模式, 一般项目中存在最多的就是这个模式.
工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中, 我们不再提供一个统一的工厂类来创建所有的对象, 而是针对不同的对象提供不同的工厂. 也就是说 每个对象都有一个与之对应的工厂 .
2.2 适用场景
一个类不知道它所需要的对象的类: 在工厂方法模式中, 客户端不需要知道具体产品类的类名, 只需要知道所对应的工厂即可, 具体的产品对象由具体工厂类创建; 客户端需要知道创建具体产品的工厂类.
一个类通过其子类来指定创建哪个对象: 在工厂方法模式中, 对于抽象工厂类只需要提供一个创建产品的接口, 而由其子类来确定具体要创建的对象, 利用面向对象的多态性和里氏
将创建对象的任务委托给多个工厂子类中的某一个, 客户端在使用时可以无需关心是哪一个工厂子类创建产品子类, 需要时再动态指定, 可将具体工厂类的类名存储在配置文件或数据库中.
2.3 工厂方法模式角色分配
抽象工厂 (Abstract Factory) 角色: 是工厂方法模式的核心, 与应用程序无关. 任何在模式中创建的对象的工厂类必须实现这个接口.
具体工厂 (Concrete Factory) 角色 : 这是实现抽象工厂接口的具体工厂类, 包含与应用程序密切相关的逻辑, 并且受到应用程序调用以创建某一种产品对象.
抽象产品 (Abstract Product) 角色 : 工厂方法模式所创建的对象的超类型, 也就是产品对象的共同父类或共同拥有的接口.
具体产品 (Concrete Product) 角色 : 这个角色实现了抽象产品角色所定义的接口. 某具体产品有专门的具体工厂创建, 它们之间往往一一对应
2.4 工厂方法模式代码实现
抽象工厂 Factory
- public interface Factory {
- Shape getShape();
- }
具体工厂 CircleFactory,SquareFactory
- public class CircleFactory implements Factory {
- @Override
- public Shape getShape() {
- return new Circle();
- }
- }
- public class SquareFactory implements Factory {
- @Override
- public Shape getShape() {
- return new Square();
- }
- }
抽象产品和具体产品继续使用简单工厂模式中的类
客户端
- public class Client {
- public static void main(String[] args) {
- Shape circle = new CircleFactory().getShape();
- circle.draw();
- Shape square = new SquareFactory().getShape();
- square.draw();
- }
- }
运行结果
圆形 正方形
3. 抽象工厂模式
3.1 介绍
在工厂方法模式中, 其实我们有一个潜在意识的意识. 那就是我们生产的都是同一类产品. 抽象工厂模式是工厂方法的仅一步深化, 在这个模式中的工厂类不单单可以创建一种产品, 而是可以创建一组产品.
将工厂抽象成两层, Abstract Factory(抽象工厂)和具体实现的工厂子类. 程序员可以根据创建对象类型使用对应的工厂子类. 这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展.
3.2 适用场景
和工厂方法一样客户端不需要知道它所创建的对象的类.
需要一组对象共同完成某种功能时, 并且可能存在多组对象完成不同功能的情况.(同属于同一个产品族的产品)
系统结构稳定, 不会频繁的增加对象.(因为一旦增加就需要修改原有代码, 不符合开闭原则)
3.3 抽象工厂模式角色分配
抽象工厂 (Abstract Factory) 角色 : 是工厂方法模式的核心, 与应用程序无关. 任何在模式中创建的对象的工厂类必须实现这个接口.
具体工厂类 (Concrete Factory) 角色 : 这是实现抽象工厂接口的具体工厂类, 包含与应用程序密切相关的逻辑, 并且受到应用程序调用以创建某一种产品对象.
抽象产品 (Abstract Product) 角色 : 工厂方法模式所创建的对象的超类型, 也就是产品对象的共同父类或共同拥有的接口.
具体产品 (Concrete Product) 角色 : 抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例. 在抽象工厂中创建的产品属于同一产品族, 这不同于工厂模式中的工厂只创建单一产品.
3.4 抽象工厂的工厂和工厂方法中的工厂有什么区别?
抽象工厂是生产一整套有产品的(至少要生产两个产品), 这些产品必须相互是有关系或有依赖的, 而工厂方法中的工厂是生产单一产品的工厂.
3.5 抽象工厂模式代码实现
抽象产品: Gun,Bullet
- public interface Gun {
- void shooting();
- }
- public interface Bullet {
- void loading();
- }
具体产品: AK47,AK47Bullet,M16,M16Bullet
- 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
- public interface ArmsFactory {
- Gun produceGun();
- Bullet produceBullet();
- }
具体工厂:
- 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();
- }
- }
测试
- 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 方法和对象序列化方式 (推荐) 实现
- // 使用 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;
- }
- // 使用序列化
- 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. 基本介绍
建造者模式 (Builder Pattern) 又叫生成器模式, 是一种对象构建模式. 它可以将复杂对象的建造过程抽象出来 (抽象类别), 使这个抽象过程的不同实现方法可以构造出不同表现(属性) 的对象.
建造者模式是一步步创建一个复杂的对象, 它允许用户只通过指定复杂对象的类型和内容就可以构造它们, 用户不需要知道内部的具体构建细节.
2. 建造者模式的四个角色
产品角色(Product): 一个具体的产品对象
抽象建造者(Builder): 创建一个 Product 对象的各个部件指定的接口或抽象类
具体建造者(Concrete Builder): 实现接口, 构建和装配各个部件.
指挥者(Director): 构建一个使用 Builder 接口的对象. 它主要是用于创建一个复杂的对象. 它主要有两个作用:1 隔离了客户与对象的生产过程,2负责控制产品对象的生产过程.
3. 原理类图:
4. 建造者模式在 JDK - StringBuilder 中的应用
在 StringBuilder 继承关系中:
StringBuilder 继承了 AbstractStringBuilder ,AbstractStringBuilder 实现了 Appendable 接口
角色分析:
抽象建造者: Appendable
具体建造者: AbstractStringBuilder
指挥者: StringBuilder
5. 建造者模式注意事项与细节
客户端 (使用程序) 不必知道产品内部组成的细节, 将产品本身与产品的创建过程解耦, 使得相同的创建过程可以创建不同的产品对象
每一个具体建造者都相对独立, 而与其他的具体建造者无关, 因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
可以更加精细地控制产品的创建过程. 将复杂产品的创建步骤分解在不同的方法中, 使得创建过程更加清晰, 也更方便使用程序来控制创建过程
增加新的具体建造者无须修改原有类库的代码, 指挥者类针对抽象建造者类编程, 系统扩展方便, 符合 "开闭原则"
6. 建造者模式代码实现
具体产品: House
- public class House {
- private String basic;
- private String wall;
- private String roofed;
- // 省略 getter setter toString()方法
- }
抽象建造者: HouseBuilder
- 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
- 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
- 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();
- }
- }
测试:
- 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 的墙
封玻璃顶
来源: https://www.cnblogs.com/songjilong/p/12531535.html