创建型模式——创建或实例化对象
整个代码和文档是跟着Bilibili up主:黑手书生 的系列视频——设计模式总结的
学习地址:https://space.bilibili.com/505571900?from=search&seid=10016231603740656139
视频中的代码地址:https://github.com/OAyUliko/JAVA_Design_pattern
总述
- 包括 简单工厂模式、单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式
- 解决了类的实例化过程中把 对象的创建 和 对象的使用 分离。整个系统只知道的是抽象类所定义的 接口 ,不知道具体 创建细节
- 处理 “ 对象怎么创建 ” 的设计模式
- 实例:
例子 | 模式 |
---|---|
只能打开一个任务管理器 | 单例 |
不能两台打印机同时打印同一份文件 | 单例+观察者 |
游戏中的不同角色,可以捏造不同的脸,但总体过程是不变的 | 建造者 |
邮件的发件人,收件人,主题,内容是不同的 | 建造者 |
JAVA中的Object clone() | 原型 |
Web中的配置,数据库连接池,线程池(因为这里都是不会被二次更改的,如果会被别的类所修改,用原型) | 单例 |
①简单工厂模式 Simple Factory
- 动机:定义一个具体的工厂类来负责创建所有类的对象并初始化创建的对象
- 客户端去调用工厂类,工厂去创建水果类对象(其实是水果某个子类对象)
//客户端代码
Factroy factroy=new Factroy();
Fruit fruit=factroy.CreatFruit("A");
fruit.eat();
- 扩展:只能在工厂类中添加新的if语句。不符合 “开闭原则”
- 类图:
②工厂方法模式 Factory Method
- 动机:让子类决定实例化哪一个类,使一个类的 实例化延迟 到其 子类
- 工厂可以 自主决定 创建何种产品,如何创建则被封装在具体工厂内部
- Client只跟 工厂 打交道,不和产品打交道
//客户端代码
Factroy factroyB=new BFactroy(); //Factroy factroy =(Factroy)UMLUtil.getBean();
Fruit fruit=factroyB.CreatFruit(); //Fruit fruit=factroy.CreatFruit();
fruit.eat();
- 扩展:只能在工厂类中添加新的if语句。不符合 “开闭原则”
- 角色:抽象工厂、具体工厂、抽象产品(核心角色)、具体产品
- 扩展:①增加新的Fruit基类,新的Factroy基类
②修改 配置文件 ,客户端中只出现基类工厂 - 优点:①用户无需关心创建细节,包括类名,只需关心对应的具体工厂类
②扩展时无需修改客户端,只需添加一个具体工厂类和具体产品类。符合 “开闭原则” - 缺点:①扩展时成对增加类的个数,系统 复杂度增加
②引入了抽象层,系统 抽象性 和 理解难度增加 - 适用场景:①一个类不知道它所需要的对象的类
②一个类由其子类来指定创建哪个对象
③类将创建对象的职责委托给多个子类,客户端可以动态指定哪个工厂创建 - 类图:
③抽象工厂模式 Abstract Factory
- 动机:提供一个创建一系列相关或相互依赖对象的 接口,而无需指定产品的 具体类型 ,便可创建 不同产品族 中的产品
//客户端代码
A_FruitVegetable a_fruitVegetable = new AFactroy();
Fruit fruit = a_fruitVegetable.createF();
Vegetable vegetable = a_fruitVegetable.createV();
fruit.eat();
vegetable.eat();
- 产品类别:水果中的Apple和Bananan就是两个类别
增加产品类别 —— 难 —— 修改所有的工厂类,不能 很好支持“开闭原则”(开闭原则的倾斜性)
产品族:同一个工厂生产的,位于不同产品等级结构中的一组产品。A工厂生产的Apple和Tomato是一族
增加产品族 —— 简单 —— 增加一个具体工厂,很好 支持 “开闭原则” - 角色:抽象工厂(接口)、具体工厂(实现接口,由Client的调用创建产品)、抽象产品(产品的共同父类)、具体产品
- 优点:①保证客户端始终使用同一个产品族中的对象
- 缺点:①产品族扩展非常困难
- 类图:
工厂方法模式和抽象工厂模式对比
- 工厂方法:是一种极端情况的抽象工厂 抽象工厂:是工厂方法模式的推广
- 工厂方法:创建一个产品的等级结构 抽象工厂:创建多个产品的等级结构
- 工厂方法:一个抽象产品类 抽象工厂:多个抽象产品类
- 工厂方法:具体工厂类只有一个创建方法 抽象工厂:具体工厂类有多个创建方法
④建造者模式 Builder Pattern
- 动机:将一个复杂对象的 构造 与它的 表示 分离,产品的 创建步骤 和 组成部分 是不变的,但每一部分是可以灵活选择的
- Client只跟Waiter打交道,它是指挥者,指挥Builder建造者去根据菜单(其实是客户指定)制造单品组成套餐Meal,返还给Waiter
//客户端代码
MealBuilder mb=new BMealBuilder(); //得到所需的套餐
Waiter waiter=new Waiter(); //创建服务员
waiter.setMealBuilder(mb); //服务员把套餐传递给Builder制作
Meal meal=waiter.construct(); //服务员获得套餐
System.out.println("这个套餐有"+meal.getFood()+meal.getDrink());
- 角色:最后的产品、抽象建造者、各种具体的建造者、指挥者(组装者)、各种零件
- 优点:①产品的建造和表示分离,实现了 解耦
②增加新的具体建造者无需修改原有类库的代码,易于拓展,符合“开闭原则“ - 缺点:①产品必须有共同点,限制了使用范围
- 适用场景:①产品有复杂的内部结构,产品对象具备共性
②适合创建具有 较多的零件 的产品 - 类图:
⑤原型模式 Prototype Pattern
- 动机: 实现了一个原型 接口,该接口用于 克隆 当前对象
- 用原型实例指定创建对象的种类,通过拷贝原型创造出更多同类型的对象
- 原型管理器:通过 Hashtable 中的key来管理对象
//客户端代码
FruitStore fs=new FruitStore();
fs.Add(1,f1); //1号苹果上架
fs.Add(2,new Apple());//2号苹果上架
Fruit fru=fs.Get(1); //取出1号水果
Fruit fru=fs.Get(2); //取出所谓的2号水果,其实取出的是复制品,Get方法中进行了浅克隆
这里1号和2号苹果虽然有不同的哈希值,是两个对象,但他们有可能指向同一个地址空间
//FruitStore类中的Get()浅克隆方法
Fruit fruit=(Fruit) fruittable.get(i);
return (Fruit)fruit.clone(); //把水果拿出来克隆了再返回 其实是个浅拷贝
//Fruit类中的clone方法
Object object=super.clone();
- 浅克隆:仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象【实现Cloneable接口】
深克隆:不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象【实现 Serializable 读取二进制流】
//浅拷贝
super.clone();
//深拷贝 Teacher类的getStudent方法创建一个学生类,这个学生类只有深拷贝才有两份,可以更改值;浅拷贝的话只有一份,无法更改值
Teacher teacher = (Teacher)super.clone();
teacher.setStudent((Student)teacher.getStudent().clone());
- 优点:①创建对象的性能提高
- 缺点:①必须实现 Cloneable 接口
- 适用场景:①资源优化场景
②创建对象(复杂且频繁)的代价较大时
③一个对象需要提供给其他对象访问,而且各个调用者可能会修改其值时,可以使用原型模式拷贝多个对象供调用者使用
④实际开发中,原型模式常与工厂方法一起出现
⑤各种各样的 池 - 类图:
⑥单例模式 Singleton Pattern
- 动机:一个类仅创建单个对象,即仅有 一个 实例,外界不能通过类的 构造函数 来 new 一个对象,而是通过提供一个 “接口” (可以是一个类)来创建它
- 水果店的构造函数一定要是私有的!!
//客户端代码
Fruit f1=new Apple();
FruitStore fs1=FruitStore.GetFruitStore();//第一个水果店,不是直接new的!
FruitStore fs2=FruitStore.GetFruitStore();//第二个水果店,但其实还是第一个,指向同一个对象
fs1和fs2的hashcode是一样的,说明是同一个对象!
//关键代码在FruitStore类中
private static Hashtable fruittable;//水果架
private static FruitStore fruitStore;//水果店
private FruitStore() { //把构造函数设为private , 这样外界就不能直接new了,只能通过GetFruitStore来获得对象
fruittable=new Hashtable<Integer,String>();
}
public static FruitStore GetFruitStore()
{
if(fruitStore==null)//如果水果店不存在,就创建一个水果店,如果存在一个实例了,就返回那个实例
{fruitStore=new FruitStore();}
return fruitStore;
}
- 优点:①内存中只有一个实例,减少内存开销
- 缺点:①不符合“单一职责原则”,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
- 饿汉式单例:①类加载时就已经创建了单例对象,等着通过“接口”去调用
②线程安全,但不管你要不要都会直接创建一个对象
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
6. 懒汉式单例:①你需要对象时才会生成单例对象(比如调用getInstance方法),也叫 延迟加载 (Lazy Load)技术
②线程不安全,为了避免多个线程同时调用getInstance()方法,可以使用synchronized
private static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;