详细整理全23种设计模式-创建型模式篇(java源码,优缺点解析,适用场景)

设计模式首先分成三类,这一篇主要讲创建型模式中的5种,创建型模式的作用就是为了产生实例对象,基于面向对象设计,将对象的创建与使用分离,降低系统的耦合度
在这里插入图片描述

单例模式

特点:

  • 单例类只有一个实例对象,减少内存开销
  • 该单例对象必须由单例类自行创建
  • 单例类对外提供一个访问该单例的全局访问点

饿汉

public class HungrySingleton {
	// 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
    private static final HungrySingleton instance = new HungrySingleton();

	// private 避免类在外部被实例化
    private HungrySingleton() {
    }

	//饿汉直接返回该实例就行了
    public static HungrySingleton getInstance() {
        return instance;
    }
    
    // 瞎写一个静态方法。只要调用,就会生成 Singleton实例
    public static Date getDate(String mode) {return new Date();}
}

缺点是只要使用该类中的其他静态方法,就已经自动实例化该类了,不过基本不会有人蠢到给单例类中塞个没啥用的静态方法

懒汉

    public  class LazySingleton {
        // volatile保证其可见性
        private static volatile LazySingleton instance = null;

        // private 避免类在外部被实例化
        private LazySingleton() {
        }

		// 懒汉会有线程不安全的问题,上锁是关键
        public static LazySingleton getInstance() {
            if (instance == null) {
                // 加锁
                synchronized (LazySingleton.class) {
                    // 这一次判断也是必须的,不然会有并发问题
                    if (instance == null) {
                        instance = new LazySingleton();
                    }
                }
            }
            return instance;
        }
        
//        //不建议直接方法加锁
//        public static synchronized LazySingleton getInstance() {
//            //getInstance 方法前加同步
//            if (instance == null) {
//                instance = new LazySingleton();
//            }
//            return instance;
//        }

    }

缺点就是锁带来的性能问题,不过也可以忽略不计

最后说一下枚举,枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。有些java源码中会用枚举来实现单例。
在这里插入图片描述
有文章分析说枚举实现单例是最佳方法,详细自行百度,这里只重点掌握饿汉和懒汉,还有很多其他的单例实现方法

建造者模式

class User {
    // 下面是“一堆”的属性
    private String name;
    private String password;
    private String nickName;
    private int age;

    // 构造方法私有化,不然客户端就会直接调用构造方法了
    private User(String name, String password, String nickName, int age) {
        this.name = name;
        this.password = password;
        this.nickName = nickName;
        this.age = age;
    }
    // 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
    // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
    public static UserBuilder builder() {
        return new UserBuilder();
    }

    public static class UserBuilder {
        // 下面是和 User 一模一样的一堆属性
        private String  name;
        private String password;
        private String nickName;
        private int age;

        private UserBuilder() {
        }

        // 链式调用设置各个属性值,返回 this,即 UserBuilder
        public UserBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserBuilder password(String password) {
            this.password = password;
            return this;
        }

        public UserBuilder nickName(String nickName) {
            this.nickName = nickName;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
        // 当然,可以在 “复制” 之前做点检验
        public User build() {
            if (name == null || password == null) {
                throw new RuntimeException("用户名和密码必填");
            }
            if (age <= 0 || age >= 150) {
                throw new RuntimeException("年龄不合法");
            }
            // 还可以做赋予”默认值“的功能
              if (nickName == null) {
                nickName = name;
            }
            return new User(name, password, nickName, age);
        }
    }
}

其实就是在Builder类中将setter方法赋值后return this

不过可以看到做必填校验的时候用建造者是很不错的,这样就不用在外部判空了,缺点是要写很多Builder类和build方法,不过可以用lombok,简洁优雅

使用@Builder注解
在这里插入图片描述
使用建造者模式,特点是链式写法
在这里插入图片描述

原型模式

原型模式的特点是基于当前类实例产生新的实例,所以用到了克隆
在这里插入图片描述
Object 类中有一个 clone() 方法,我们用他来生成新的对象,同时我们的类必须先实现 Cloneable 接口
在这里插入图片描述
该接口无任何内容,只是作为标记,如果不实现,则会抛出CloneNotSupportedException异常

protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }
 
        return internalClone();
    }

这里要说的是 java 的克隆是浅克隆,碰到对象引用的时候,克隆出来的对象和原对象中的引用将指向同一个对象。通常实现深克隆的方法是将对象进行序列化,然后再进行反序列化。

可以看我之前写的文章:
Java对象拷贝复习,深/浅拷贝及深拷贝的三种实现方式
java克隆/拷贝,类型转换

	//具体原型类
    @Data
    public class Realizetype implements Cloneable {
        private String name;

        Realizetype() {
            System.out.println("具体原型创建成功!");
        }

        public Object clone() throws CloneNotSupportedException {
            System.out.println("具体原型复制成功!");
            return super.clone();
        }
    }

    //原型模式的测试类
    public class PrototypeTest {
        public static void main(String[] args) throws CloneNotSupportedException {
            Realizetype obj1 = new Realizetype();
            obj1.setName("111");
            Realizetype obj2 = (Realizetype) obj1.clone();
            obj2.setName("222");
            System.out.println(obj1.getName());
            System.out.println(obj2.getName());
            System.out.println("obj1==obj2?" + (obj1 == obj2));
        }
    }

obj1不等于obj2
在这里插入图片描述

工厂方法

工厂模式是循序渐进的,需要按照简单工厂-工厂方法-抽象工厂的顺序去理解

先介绍下简单工厂

public class Client {
    //抽象产品
    public interface Product {
        void show();
    }

    //具体产品:Product1
    static class Product1 implements Product {
        public void show() {
            System.out.println("具体产品1显示...");
        }
    }

    //具体产品:Product2
    static class Product2 implements Product {
        public void show() {
            System.out.println("具体产品2显示...");
        }
    }

    final class Const {
        static final int PRODUCT_A = 0;
        static final int PRODUCT_B = 1;
        static final int PRODUCT_C = 2;
    }

    static class SimpleFactory {
        public static Product makeProduct(int kind) {
            switch (kind) {
                case Const.PRODUCT_A:
                    return new Product1();
                case Const.PRODUCT_B:
                    return new Product2();
            }
            return null;
        }
    }
}

其实就是一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。

这里要强调职责单一原则,一个类只提供一种功能,即FoodFactory只产出FoodA,FoodB,ProductFactory只产出ProductA,ProductB

而工厂模式则需要使用两个或两个以上的工厂,因此我们创建一个工厂类的接口,来实现不同的工厂,单个工厂类就用简单工厂模式就好了

interface AbstractFactory {
    public Product newProduct();
}

然后先创建两个工厂

class Factory1 implements AbstractFactory {
    public Product newProduct() {
        System.out.println("工厂1生成产品1");
        return new Product1();
    }
}

class Factory2 implements AbstractFactory {
    public Product newProduct() {
        System.out.println("工厂2生成产品2");
        return new Product2();
    }
}

main方法测试一下

public static void main(String[] args) {
        try {
            Product a;
            AbstractFactory af;
            af = (AbstractFactory) ReadXML1.getObject();
            a = af.newProduct();
            a.show();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
}

这里使用xml配置文件中写类名的方式来返回类

import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;

class ReadXML1 {
    //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
    public static Object getObject() {
        try {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File("src/FactoryMethod/config1.xml"));
            //获取包含类名的文本节点
            NodeList nl = doc.getElementsByTagName("className");
            Node classNode = nl.item(0).getFirstChild();
            String cName = "类所在的当前包名." + classNode.getNodeValue();
            //System.out.println("新类名:"+cName);
            //通过类名生成实例对象并将其返回
            Class<?> c = Class.forName(cName);
            Object obj = c.newInstance();
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

xml如下,作为配置文件传参,传个Factroy1
在这里插入图片描述
总结:一个工厂接口,需要2个以上的工厂实现,每个工厂生产自己拥有的多个不同产品
在这里插入图片描述

抽象工厂

最后介绍抽象工厂,在没使用抽象工厂前,使用工厂方法实现如下
在这里插入图片描述

// 得到 Intel 的 CPU
CPUFactory intelCPUFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();

// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.makeMainBoard();

// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);

上面是得到硬件去组装cpu的过程,但实际情况会存在硬件兼容问题,这时候需要引入产品族的概念
在这里插入图片描述
抽象工厂则是将cpu作为工厂类,硬件类在内部实例化

在这里插入图片描述

public static void main(String[] args) {
    // 第一步就要选定一个“大厂”
    ComputerFactory cf = new AmdFactory();
    // 从这个大厂造 CPU
    CPU cpu = cf.makeCPU();
    // 从这个大厂造主板
    MainBoard board = cf.makeMainBoard();
    // 从这个大厂造硬盘
    HardDisk hardDisk = cf.makeHardDisk();

    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
    Computer result = new Computer(cpu, board, hardDisk);
}

对比发现:

  • 工厂方法是各生产各的,cpu/主板等硬件是由各自的工厂生产
  • 抽象工厂是成套式组装的,cpu/主板等硬件全在一个工厂里完成

个人理解产品族概念:

  • 其实可以理解成套装/套餐,假如一个套餐内包含主食+饮料,a套餐里包含三明治+可乐,b套餐包含汉堡+果汁
  • 工厂方法是将主食,饮料作为工厂,生产好后再组合成套餐
  • 抽象工厂是将a,b套餐作为工厂,每个套餐生产各自的主食和饮料

最后总结下工厂模式(简单工厂,工厂方法,抽象工厂)

简单工厂:

  • 优点: 工厂和产品的职责区分明确;只需简单传参就能生产需要的产品;可以引入配置文件,在不修改代码的情况下更换和添加新的具体产品类
  • 缺点:职责单一,只能提供简单的功能;方法使用static,工厂无法继承
  • 使用场景:产品种类相对较少,一个工厂类就能解决的场景下

工厂方法:

  • 优点:灵活性强,创建新产品,只需多写相应的工厂类;简单工厂的增强
  • 缺点:相比简单工厂增加了复杂度;产品之间不兼容
  • 使用场景:需要使用两个或以上工厂时

抽象工厂:

  • 优点:管理产品族方便;增强了程序的可扩展性,对扩展开放
  • 缺点:再加一个产品时需要修改大量代码,对修改关闭
  • 使用场景:套餐,产品不兼容等情况下

参考:
创建型模式应用实验
25000 字详解 23 种设计模式(多图 + 代码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值