创建型模式可分为:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
单例模式
单例模式
- 就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个获取其对象的方法(静态方法)
- 单例模式共有8中写法
- 对于一些重量级的对象,可以使用单例模式
单例模式的8种写法
- 单例模式可以避免很重量级的对象频繁创建
饿汉式写法
- 私有构造器
- 类在内部创建静态对象
- 提供一个共有的静态方法,获取对象的方法
- 优点:
- 写法简单,在装载类的时候就完成了实例化,避免了多线程问题
- 缺点:
- 载类加载的时候就完成实例化,没有懒加载的效果,如果从始至终都没有使用过这个实例,会造成内存的浪费
- 这种方式线程安全可用,但是有可能会造成内存浪费
饿汉式有两种写法:
- 静态变量写法
java
/**
* 饿汉单例模式,静态变量写法
*/
public class HungryMan {
private static HungryMan hungryMan =new HungryMan();
private HungryMan(){
}
public static HungryMan getInstance(){
return hungryMan;
}
}
- 静态代码块写法
java
/**
* 饿汉单例模式,静态代码块写法
*/
public class HungryMan1 {
private static HungryMan1 hungryMan1;
static {
HungryMan1 hungryMan1 =new HungryMan1();
}
private HungryMan1(){
}
public static HungryMan1 getInstance(){
return hungryMan1;
}
}
懒汉式
懒汉式,线程不安全写法
- 优点:
- 起到了懒加载的作用,只有用到了实例,才会创建对象
- 缺点:
- 只能在单线程下使用,多线程下会有线程安全问题
- 多线程下如果A线程获取对象实例,判断对象没有实例,但是还未来得及创建对象,此时B线程也来获取对象实例,同样会判断没有实例,这样就会创建多个对象,
- 所以在实际开发中不要使用这种方式
java
/**
* 懒汉式,线程不安全
*/
public class LazyMan {
private LazyMan() {
}
private static LazyMan lazMan;
public static LazyMan getInstance() {
if (lazMan == null) {
// 这里增加是睡眠是为了验证线程不安全,实际上不加
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lazMan = new LazyMan();
}
return lazMan;
}
}
如下得到了两个对象
懒汉式,线程安全写法
-
给获取对象的方法枷锁
-
优点:保证了线程安全
-
缺点:每次获取实例的时候都需要获取锁,然而实际上只有第一次才需要加锁,后续获取对象直接返回就好了,所以会存在性能问题
-
所以并不推荐这种方法
java
/**
* 懒汉式,线程安全写法
*/
public class LazyMan1 {
private LazyMan1() {
}
private static LazyMan1 lazMan1;
public static synchronized LazyMan1 getInstance() {
if (lazMan1 == null) {
// 这里增加是睡眠是为了验证线程安全,实际上不加
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lazMan1 = new LazyMan1();
}
return lazMan1;
}
}
多个线程,得到的是一个对象,线程安全
懒汉式,线程不安全,同步代码块写法
- 在同步代码块种加锁,并没有解决线程安全问题,所以在实际开发中不要使用这种方式
java
/**
* 懒汉式,线程不安全,同步代码块写法
*/
public class LazyMan2 {
private LazyMan2() {
}
private static LazyMan2 lazMan2;
public static LazyMan2 getInstance() {
if (lazMan2 == null) {
// 这里增加是睡眠是为了验证线程安全,实际上不加
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 给代码块加锁
synchronized (LazyMan2.class){
lazMan2 = new LazyMan2();
}
}
return lazMan2;
}
}
双检测写法
- 给变量加上volatile
- 先判断对象是否存在,对象存在就直接返回,不会每次都需要加锁从而影响效率
- 对象不存在就去获取锁,获取到锁了再次判断对象是否存在,存在就说明有其他线程初始化了对象,直接返回对象
- 不存在就实例化对象,这样就保证了效率和线程安全,并且做到了和懒加载,推荐使用这个写法
java
/**
* 懒汉式,双检测写法,线程安全
*/
public class LazyMan3 {
private LazyMan3() {
}
private static volatile LazyMan3 lazMan3;
public static LazyMan3 getInstance() {
// 先判断对象是否存在,存在就直接返回,不存在再去获取锁,做到了懒加载和线程安全
if (lazMan3 == null) {
// 给代码块加锁
synchronized (LazyMan3.class){
// 在同步代码块中再次判断对象收存在
if (lazMan3 == null) {
lazMan3 = new LazyMan3();
}
}
}
return lazMan3;
}
}
静态内部类的单例模式
静态内部类写法:
- 在外部类被装载的时候,静态内部类并不会被装载
- 只有在用到静态内部类的时候,才会去装载它,而且类装载的时候是线程安全的
- 所以这种写法,能同时满足懒加载并且保证线程安全,推荐使用
java
/**
* 单例模式,静态内部类写法
*/
public class StaticInternalClass {
private StaticInternalClass(){
}
private static class InternalClass {
private static final StaticInternalClass staticInternalClass = new StaticInternalClass();
}
public static StaticInternalClass getInstance() {
return InternalClass.staticInternalClass;
}
}
枚举的单例模式
- 借助jdk1.5提供的枚举可以实现单例模式
- 枚举可以避免多线程同步问题,并且可以防止反序列化重新创建对象
- 这种方式是提倡的方式
java
public enum SingleEnum {
RED("红色", 1),
GREEN("绿色", 2),
BLANK("白色", 3),
YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private SingleEnum(String name, int index) {
this.name = name;
this.index = index;
}
}
单例模式的注意事项
可以使用的写法:
- 饿汉式写法
- 懒汉式的双重检测
- 静态内部类的写法
- 枚举
单例模式的注意事项
- 单例模式保证了系统内部中该类只存在一个对象,节省了资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高性能
- 单例模式使用场景:需要频繁创建和销毁对象、创建对象耗时很多或者耗费资源很多,但又经常用到的对象,例如工具类对象、数据源等
工厂设计模式
工厂模式的意义:
- 将对象实例化的代码提取出来,进行统一管理,达到解耦的目的,提高项目的扩展和维护性
三种工厂模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
根据依赖倒转原则:要依赖抽象不要依赖具体,创建对象时不要直接New,而是放在一个工厂里
简单工厂模式
- 也叫静态工厂模式,属于创建型模式,是工厂模式的一种
- 简单工厂模式是工厂模式中最简单也是最实用的(开放银行,协议签署时就用到了工厂模式)
- 是由一个工厂对象决定创建出哪一种产品的实例:定义一个创建对象的类,由这个类来实例化对象
- 当我们会用到大量的创建某种对象时,就可以使用工厂模式
实例:
- 如下,后续新增新的是实现,也只需要修改工厂类即可,使用方不需要修改,增强了可扩展性
java
public class Factory {
// 根据类型创建对应的对象
public static TestFactory getTestFactory(String type) {
TestFactory testFactory = null;
switch (type) {
case "1":
testFactory = new TestFactoryImplOne();
break;
case "2":
testFactory = new TestFactoryImplTwo();
break;
}
return testFactory;
}
public static void main(String[] args) {
getTestFactory("1").test();
getTestFactory("2").test();
}
}
public interface TestFactory {
void test();
}
public class TestFactoryImplOne implements TestFactory {
@Override
public void test() {
System.out.println("TestFactoryImplOne 执行");
}
}
public class TestFactoryImplTwo implements TestFactory{
@Override
public void test() {
System.out.println("TestFactoryImplTwo 执行");
}
}
工厂方法模式
工厂方法模式
- 定义一个创建对象的抽象方法,由子类决定要实例化的类
- 工厂方法模式将对象实例化推迟到子类
- 在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
工厂方法的模式结构
- 抽象工厂(Factory)角色:是工厂 方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
- 具体工厂(ConcreteCreator)角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。
- 抽象产品(Product)角色:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
- 具体产品(ConcreteProduct)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。
工厂类
java
/**
* 抽象工厂
*/
public interface Factory {
Product createProduct(String type);
}
/**
* 具体工厂1
*/
public class ConcreteFactory1 implements Factory {
@Override
public Product createProduct(String type) {
Product product = null;
switch (type) {
case "A":
product = new ProductA();
break;
case "B":
product = new ProductB();
break;
}
return product;
}
}
/**
* 具体工厂2
*/
public class ConcreteFactory2 implements Factory {
@Override
public Product createProduct(String type) {
Product product = null;
switch (type) {
case "C":
product = new ProductC();
break;
case "D":
product = new ProductD();
break;
}
return product;
}
}
产品类
java
public interface Product {
void test();
}
public class ProductA implements Product{
@Override
public void test() {
System.out.println("执行ProductA逻辑");
}
}
public class ProductB implements Product{
@Override
public void test() {
System.out.println("执行ProductB逻辑");
}
}
public class ProductC implements Product{
@Override
public void test() {
System.out.println("执行ProductC逻辑");
}
}
public class ProductD implements Product{
@Override
public void test() {
System.out.println("执行ProductD逻辑");
}
}
抽象工厂模式
抽象工厂模式:
- 定义一个接口用于创建相关或有依赖关系的对象
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整和
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进
- 将工厂分为两层:抽象工厂和具体实现的工厂子类,这样可以把的单个的简单工厂变成工厂簇,更利于代码的维护和扩展
抽象工厂模式的优点包括:
- 提供了一种封装对象创建过程的方式:抽象工厂模式将对象的创建过程封装在了具体的工厂类中,客户端只需要关注工厂接口和产品接口,无需关心具体的实现细节。
- 实现了产品族的切换:通过使用不同的具体工厂类,可以方便地切换整个产品族的产品,从而实现了接口与实现的分离。
保持了代码的一致性:抽象工厂模式保持了一致性,即所有由同一工厂创建的产品都相互关联,使得产品之间可以很方便地进行配合使用。
缺点:
- 抽象工厂模式的最大缺点就是产品族扩展非常困难,我们以通用代码为例,如果要增加一个产品C,也就是说产品等级结构由原来的2个增加到3个。此时,抽象类AbstractFactory要增加一个方法createProductC(),然后具体工厂实现类都要修改,这严重违反了开闭原则,会带来较大的不便。
- 增加了系统的复杂度:抽象工厂模式引入了多个抽象和具体类,增加了系统的复杂度和理解难度。
工厂模式的退化
- 当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式。
- 当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
抽象工厂模式与工厂方法模式的区别
- 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。
- 当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。
原型模式
java中的Object类是所有类的父类,并且提供了一个clone()方法,用于对象的拷贝,但是想要实现clone的类必须实现Cloneable接口,该接口表示该类能够复制且具有复制的能力
原型模式(prototype)
-
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,属于创建型模式
-
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。
-
当直接创建对象的代价比较大时,则采用这种模式简化对象的创建过程
-
实现克隆操作:在 Java 中,实现
Cloneable
接口,重写clone()
方法,默认为浅拷贝。 -
但是原型模式需要给每个类配置一个克隆方法,对于新类不是问题,但是对已有的类,需要修改源代码,违背了OCP原则
java
/**
* 原型模式
*/
public class PrototypeTest implements Cloneable {
private String name;
private Integer age;
public PrototypeTest(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "PrototypeTest{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public PrototypeTest clone() {
try {
return (PrototypeTest) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
/**
* 对象的属性一样,但是对象并不是同一个
* @param args
*/
public static void main(String[] args) {
PrototypeTest prototypeTest =new PrototypeTest("lisi",20);
System.out.println(prototypeTest.hashCode()+"---"+prototypeTest);
PrototypeTest clone = prototypeTest.clone();
System.out.println(clone.hashCode()+"---"+clone);
}
浅拷贝
- 指在拷贝对象时,仅复制对象本身和其内部的基本数据类型字段,而不会复制引用类型字段所引用的对象
- 这意味着新对象和原始对象会共享同一个引用对象,修改其中一个对象的引用字段会影响到另一个对象。
- 简单来说,浅拷贝只是创建了一个指向原始对象的新对象的引用
- 在 Java 中,实现浅拷贝的一种常见方式是通过重写对象的
clone()
方法,并实现Cloneable
接口。这样,当调用clone()
方法时,会创建一个新的对象,但新对象和原始对象共享相同的引用 - 浅拷贝的特点是效率较高且节省内存,因为它只是复制了对象的引用而不是对象本身。然而,这也意味着对原始对象的修改会影响到拷贝对象,反之亦然。因此,浅拷贝适用于那些不需要独立修改的对象,或者对象的结构相对简单的情况。
深拷贝(Deep Copy):
- 深拷贝是指在拷贝对象时,不仅复制对象本身和其内部的基本数据类型字段,还会递归复制引用类型字段所引用的对象。这样,新对象和原始对象将完全独立,对任何一个对象的修改都不会影响到另一个对象。
- 简而言之,深拷贝会创建一个全新的对象及其所有关联的对象。
- 实现深拷贝的方式可以是通过实现Cloneable接口并重写clone()方法,或者使用序列化和反序列化等方法来复制对象及其引用的对象。需要根据具体的需求选择适合的方式进行深拷贝操作。
- 需要注意的是,浅拷贝和深拷贝的概念适用于对象的拷贝操作,而不同于对象的赋值操作。在赋值操作中,无论是基本数据类型还是引用类型,都只是将一个对象的引用赋值给了另一个对象,它们仍然指向同一个对象,修改其中一个对象会影响到另一个对象。
深拷贝的实现方式:
- clone
- 序列化和反序列化:先把对象序列化为流,再把流反序列化变成对象,这样就可以完成深拷贝
clone方式:
java
@AllArgsConstructor
public class User implements Cloneable {
private String name;
private Role role;
public void setRole(Role role) {
this.role = role;
}
public Role getRole() {
return role;
}
@Override
public User clone() {
try {
User clone = (User) super.clone();
// 把role对象单独复制一份
clone.setRole(clone.getRole().clone());
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
@AllArgsConstructor
public class Role implements Cloneable{
// 角色名
private String roleName;
// 权限
private String power;
@Override
public Role clone() {
try {
Role clone = (Role) super.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
测试方法:
java
public static void main(String[] args) {
User user = new User("lisi", new Role("admin", "all"));
System.out.println(user.hashCode() + "---" + user + ",role:" + user.getRole().hashCode());
User clone = user.clone();
System.out.println(clone.hashCode() + "---" + clone + ",role:" + user.getRole().hashCode());
}
可以看到两个对象是相互独立的
建造者模式
建造者模式
- 建造者模式是一种创建型设计模式,它的主要目的是将一个复杂对象的构建过程与其表示相分离,从而可以创建具有不同表示形式的对象。
- 使用场景:需要生成的对象具有复杂的内部结构,需要生成的对象内部属性相互依赖。
优点
-
分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示:客户端(使用者)不需要知道产品内部组成的细节,将产品本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
-
可以更好地控制构建过程,隐藏具体构建细节。
-
每一个建造者都是相对独立的,可以很方便的替换具体建造者或增加新的具体建造者
-
代码复用性高,可以在不同的构建过程中重复使用相同的建造者。
缺点
- 如果产品的属性较少,建造者模式可能会导致代码冗余。
- 增加了系统的类和对象数量。
建造者模式包含以下几个主要角色:
- 产品(Product):要构建的复杂对象。产品类通常包含多个部分或属性。
- 抽象建造者(Builder):定义了构建产品的抽象接口,包括构建产品的各个部分的方法。
- 具体建造者(Concrete Builder):实现抽象建造者接口,具体确定如何构建产品的各个部分,并负责返回最终构建的产品。
- 指导者(Director):负责调用建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式。
建造者模式与工厂模式的区别
- 工厂方法模式注重是整体对象的创建方式;
- 而建造者模式注重的是部件一步一步的创建出一个复杂对象的过程
示例:
java
/**
* 产品
*/
@Data
public class House {
// 地基
private String foundation;
// 墙壁
private String wall;
// 屋顶
private String roof;
// 装修
private String renovation;
}
/**
* 抽象建造者
*/
public interface Builder {
// 构建地基
String foundation();
// 构建墙壁
String wall();
// 构建屋顶
String roof();
// 构建装修
String renovation();
}
/**
* 具体建造者1
*/
public class BigBuilderImpl implements Builder {
@Override
public String foundation() {
return "大房子,打五米地基";
}
@Override
public String wall() {
return "用混凝土浇筑墙体";
}
@Override
public String roof() {
return "房顶是预制板";
}
@Override
public String renovation() {
return "装修很豪华";
}
}
/**
* 具体建造者2
*/
public class SmallBuilderImpl implements Builder {
@Override
public String foundation() {
return "小房子,打两米地基";
}
@Override
public String wall() {
return "砌砖墙";
}
@Override
public String roof() {
return "房顶是水泥板";
}
@Override
public String renovation() {
return "装修比较简陋";
}
}
/**
* 指导者,复则构建产品
*/
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public House createSession() {
House house = new House();
house.setFoundation(builder.foundation());
house.setWall(builder.wall());
house.setRoof(builder.roof());
house.setRenovation(builder.renovation());
return house;
}
}
// 测试类
public class Test {
public static void main(String[] args) {
House h0 = new Director(new BigBuilderImpl()).createHouse();
System.out.println(h0);
House h1 = new Director(new SmallBuilderImpl()).createHouse();
System.out.println(h1);
}
}