2.创建者模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
2.1 单例设计模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
2.1.1饿汉式
饿汉式-方式1(静态变量方式)
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
/**
* 饿汉式
* 静态变量创建类的对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance = new Singleton();
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
饿汉式-方式2(静态代码块方式)
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。
/**
* 恶汉式
* 在静态代码块中创建该类对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
static {
instance = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
饿汉式-方式3(枚举)
首先,枚举方式是饿汉式单例模式,如果不考虑浪费内存空间的问题,这是极力推荐的单例实现模式。
因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单,而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
public enum Singleton {
INSTANCE;
}
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance == instance1);
}
2.1.2懒汉式
懒汉式-方式1(线程不安全)
/**
* 懒汉式
* 线程不安全
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式-方式2(线程安全)
/**
* 懒汉式
* 线程安全
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
//对外提供静态方法获取该对象
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式-方式3(双重检查锁)
再来讨论一下懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
/**
* 双重检查方式
*/
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和**有序性**。
public class Singleton {
//私有构造方法
private Singleton() {}
private static volatile Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为null
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
懒汉式-方式4(静态内部类)
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
package com.it.pattern.singleton.demo5;
/**
* @version v1.0
* @ClassName: Singleton
* @Description: 静态内部类方式
* @Author:
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1);
}
2.1.3破坏单例模式
序列化破坏单例
package com.it.pattern.singleton.demo7;
import java.io.Serializable;
/**
* @version v1.0
* @ClassName: Singleton
* @Description: 静态内部类方式
* @Author:
*/
public class Singleton implements Serializable {
//私有构造方法
private Singleton() {}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
解决:
package com.it.pattern.singleton.demo7;
import java.io.Serializable;
/**
* @version v1.0
* @ClassName: Singleton
* @Description: 静态内部类方式
* @Author:
*/
public class Singleton implements Serializable {
//私有构造方法
private Singleton() {}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
//当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
package com.it.pattern.singleton.demo7;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @version v1.0
* @ClassName: Client
* @Description:
* 测试使用序列化破坏单例模式
*
* @Author:
*/
public class Client {
public static void main(String[] args) throws Exception {
// writeObject2File();
Singleton s1 = readObjectFromFile();
Singleton s2 = readObjectFromFile();
System.out.println(s1 == s2); // false
}
//从文件读取数据(对象)
public static Singleton readObjectFromFile() throws Exception {
//1,创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\10172\\Desktop\\a.txt"));
//2,读取对象
Singleton instance = (Singleton) ois.readObject();
System.out.println(instance);
//释放资源
ois.close();
return instance;
}
//向文件中写数据(对象)
public static void writeObject2File() throws Exception {
//1,获取Singleton对象
Singleton instance = Singleton.getInstance();
//2,创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\10172\\Desktop\\a.txt"));
//3,写对象
oos.writeObject(instance);
//4,释放资源
oos.close();
}
}
com.it.pattern.singleton.demo7.Singleton@5b6f7412
com.it.pattern.singleton.demo7.Singleton@27973e9b
使用反射破坏单例
package com.it.pattern.singleton.demo8;
/**
* @version v1.0
* @ClassName: Singleton
* @Description: 静态内部类方式
* @Author:
*/
public class Singleton {
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
package com.it.pattern.singleton.demo8;
import java.lang.reflect.Constructor;
/**
* @version v1.0
* @ClassName: Client
* @Description:
* 测试使用反射破坏单例模式
* @Author:
*/
public class Client {
public static void main(String[] args) throws Exception {
//1,获取Singleton的字节码对象
Class clazz = Singleton.class;
//2,获取无参构造方法对象
Constructor cons = clazz.getDeclaredConstructor();
//3,取消访问检查
cons.setAccessible(true);
//4,创建Singleton对象
Singleton s1 = (Singleton) cons.newInstance();
Singleton s2 = (Singleton) cons.newInstance();
System.out.println(s1 == s2); //如果返回的是true,说明并没有破坏单例模式,如果是false,说明破坏了单例模式
}
}
解决:
package com.it.pattern.singleton.demo8;
/**
* @version v1.0
* @ClassName: Singleton
* @Description: 静态内部类方式
* @Author:
*/
public class Singleton {
private static boolean flag = false;
//私有构造方法
private Singleton() {
synchronized (Singleton.class) {
//判断flag的值是否是true,如果是true,说明非第一次访问,直接抛一个异常,如果是false的话,说明第一次访问
if (flag) {
throw new RuntimeException("不能创建多个对象");
}
//将flag的值设置为true
flag = true;
}
}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
2.2工厂模式
需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类:美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】
再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
具体类的设计如下:
在 Java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重。假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。
如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。所以说,工厂模式最大的优点就是:解耦。
package factory.before;
/**
* @version v1.0
* @ClassName: Coffee
* @Description: 咖啡类
* @Author:
*/
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
package factory.before;
/**
* @version v1.0
* @ClassName: AmericanCoffee
* @Description: 没事咖啡
* @Author:
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式咖啡";
}
}
package factory.before;
/**
* @version v1.0
* @ClassName: LatteCoffee
* @Description: 拿铁咖啡
* @Author:
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁咖啡";
}
}
package factory.before;
/**
* @version v1.0
* @ClassName: CoffeeStore
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CoffeeStore {
public Coffee orderCoffee(String type) {
//依赖具体类AmericanCoffee、LatteCoffee
//声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象
Coffee coffee = null;
if("american".equals(type)) {
coffee = new AmericanCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有");
}
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
package factory.before;
/**
* @version v1.0
* @ClassName: Client
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Client {
public static void main(String[] args) {
//1,创建咖啡店类
CoffeeStore store = new CoffeeStore();
//2,点咖啡
Coffee coffee = store.orderCoffee("american");
System.out.println(coffee.getName());
}
}
2.2.1 简单工厂
简单工厂不是一种设计模式,反而比较像是一种编程习惯。
package factory.simple_factory;
/**
* @version v1.0
* @ClassName: Coffee
* @Description: 咖啡类
* @Author:
*/
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
package factory.simple_factory;
/**
* @version v1.0
* @ClassName: AmericanCoffee
* @Description: 没事咖啡
* @Author:
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式咖啡";
}
}
package factory.simple_factory;
/**
* @version v1.0
* @ClassName: LatteCoffee
* @Description: 拿铁咖啡
* @Author:
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁咖啡";
}
}
package factory.simple_factory;
/**
* @version v1.0
* @ClassName: CoffeeStore
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CoffeeStore {
public Coffee orderCoffee(String type) {
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
//调用生产咖啡的方法
Coffee coffee = factory.createCoffee(type);
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
package factory.simple_factory;
/**
* @version v1.0
* @ClassName: SimpleCoffeeFactory
* @Description: 简单咖啡工厂类,用来生产咖啡
* @Author:
*/
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
//声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象
Coffee coffee = null;
if("american".equals(type)) {
coffee = new AmericanCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有");
}
return coffee;
}
}
在以上代码中,工厂处理创建对象的细节,一旦有了 SimpleCoffeeFactory,CoffeeStore 类中的 orderCoffee() 就变成此对象的客户,后期如果需要 Coffee 对象直接从工厂中获取即可,这样也就解除了和 Coffee 实现类的耦合。
但是又产生了新的耦合,CoffeeStore 对象和 SimpleCoffeeFactory 工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们需要修改 SimpleCoffeeFactory 的代码,违反了开闭原则。美团外卖有咖啡,甜品店有咖啡。
2.2.2 工厂方法模式
package factory.factory_method;
/**
* @version v1.0
* @ClassName: Coffee
* @Description: 咖啡类
* @Author:
*/
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: AmericanCoffee
* @Description: 没事咖啡
* @Author:
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式咖啡";
}
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: LatteCoffee
* @Description: 拿铁咖啡
* @Author:
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁咖啡";
}
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: CoffeeFactory
* @Description: CoffeeFactory : 抽象工厂
* @Author:
*/
public interface CoffeeFactory {
//创建咖啡对象的方法
Coffee createCoffee();
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: AmericanCoffeeFactory
* @Description: 美式咖啡工厂对象,专门用来生产美式咖啡
* @Author:
*/
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: LatteCoffeeFactory
* @Description: 拿铁咖啡工厂,专门用来生产拿铁咖啡
* @Author:
*/
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: CoffeeStore
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CoffeeStore {
private CoffeeFactory factory;
public void setFactory(CoffeeFactory factory) {
this.factory = factory;
}
//点咖啡功能
public Coffee orderCoffee() {
Coffee coffee = factory.createCoffee();
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
package factory.factory_method;
/**
* @version v1.0
* @ClassName: Client
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Client {
public static void main(String[] args) {
//创建咖啡店对象
CoffeeStore store = new CoffeeStore();
//创建对象
//CoffeeFactory factory = new AmericanCoffeeFactory();
CoffeeFactory factory = new LatteCoffeeFactory();
store.setFactory(factory);
//点咖啡
Coffee coffee = store.orderCoffee();
System.out.println(coffee.getName());
}
}
优点:
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,增加了系统的复杂度。
2.2.3 抽象工厂
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: Coffee
* @Description: 咖啡类
* @Author:
*/
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: AmericanCoffee
* @Description: 没事咖啡
* @Author:
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式咖啡";
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: LatteCoffee
* @Description: 拿铁咖啡
* @Author:
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁咖啡";
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: Dessert
* @Description: 甜品抽象类
* @Author:
*/
public abstract class Dessert {
public abstract void show();
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: MatchaMousse
* @Description: 抹茶慕斯类
* @Author:
*/
public class MatchaMousse extends Dessert {
public void show() {
System.out.println("抹茶慕斯");
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: Trimisu
* @Description: 提拉米苏类
* @Author:
*/
public class Trimisu extends Dessert {
public void show() {
System.out.println("提拉米苏");
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: DessertFactory
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public interface DessertFactory {
//生产咖啡的功能
Coffee createCoffee();
//生产甜品的功能
Dessert createDessert();
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: AmericanDessertFactory
* @Description:
* 美式风味的甜品工厂
* 生产美式咖啡和抹茶慕斯
* @Author:
*/
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: ItalyDessertFactory
* @Description:
*
* 意大利风味甜品工厂
* 生产拿铁咖啡和提拉米苏甜品
* @Author:
*/
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Trimisu();
}
}
package factory.abstract_factory;
/**
* @version v1.0
* @ClassName: Client
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Client {
public static void main(String[] args) {
//创建的是意大利风味甜品工厂对象
//ItalyDessertFactory factory = new ItalyDessertFactory();
AmericanDessertFactory factory = new AmericanDessertFactory();
//获取拿铁咖啡和提拉米苏甜品
Coffee coffee = factory.createCoffee();
Dessert dessert = factory.createDessert();
System.out.println(coffee.getName());
dessert.show();
}
}
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
想要生产一个产品族的产品,只需要一个接口或类。
缺点:
当产品族中需要增加一个新的产品(油条)时,所有的工厂类都需要进行修改。
2.2.4 扩展:简单工厂 + 配置文件
可以通过工厂模式 + 配置文件的方式解除工厂对象和产品对象的耦合。
在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
package factory.config_factory;
/**
* @version v1.0
* @ClassName: Coffee
* @Description: 咖啡类
* @Author:
*/
public abstract class Coffee {
public abstract String getName();
//加糖
public void addsugar() {
System.out.println("加糖");
}
//加奶
public void addMilk() {
System.out.println("加奶");
}
}
package factory.config_factory;
/**
* @version v1.0
* @ClassName: AmericanCoffee
* @Description: 没事咖啡
* @Author:
*/
public class AmericanCoffee extends Coffee {
public String getName() {
return "美式咖啡";
}
}
package factory.config_factory;
/**
* @version v1.0
* @ClassName: LatteCoffee
* @Description: 拿铁咖啡
* @Author:
*/
public class LatteCoffee extends Coffee {
public String getName() {
return "拿铁咖啡";
}
}
package factory.config_factory;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
/**
* @version v1.0
* @ClassName: CoffeeFactory
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CoffeeFactory {
//加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
//1,定义容器对象存储咖啡对象
private static HashMap<String,Coffee> map = new HashMap<String, Coffee>();
//2,加载配置文件, 只需要加载一次
static {
//2.1 创建Properties对象
Properties p = new Properties();
//2.2 调用p对象中的load方法进行配置文件的加载
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//从p集合中获取全类名并创建对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
String className = p.getProperty((String) key);
//通过反射技术创建对象
Class clazz = Class.forName(className);
Coffee coffee = (Coffee) clazz.newInstance();
//将名称和对象存储到容器中
map.put((String)key,coffee);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//根据名称获取对象
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
package factory.config_factory;
/**
* @version v1.0
* @ClassName: Client
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Client {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee("american");
System.out.println(coffee.getName());
System.out.println("=============");
Coffee latte = CoffeeFactory.createCoffee("latte");
System.out.println(latte.getName());
}
}
2.2.5应用 JDK源码 - Collection.iterator
Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
另外:
1,DateForamt类中的getInstance()方法使用的是工厂模式;
2,Calendar类中的getInstance()方法使用的是工厂模式;
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
理解: 即Collection是最底层抽象,Iterator又是功能实现的抽象,即大抽象包含小抽象。
List继承Collection,在最基础的数据结构上添加自己的数据结构, ArrayList实现List中所有接口,也拥有了小抽象,就可以对小抽象进行具体实现,实现方式为内部类。
2.3 原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
使用场景
1.对象的创建非常复杂。
2.性能和安全要求比较高。
2.3.1 结构
抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
访问类:使用具体原型类中的 clone() 方法来复制新的对象。
2.3.2实现
原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
浅克隆类似 “快捷方式”,深克隆才是真正的 “复制文件”。
Java 中的 Object 类中提供了 clone() 方法来实现浅克隆。
Java 中的 Cloneable 接口是上面类图中的抽象原型类,而实现了 Cloneable 接口的子实现类就是具体的原型类。
package com.itheima.pattern.prototype.demo;
/**
* @version v1.0
* @ClassName: client
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class client {
public static void main(String[] args) throws CloneNotSupportedException {
//创建一个原型类对象
Realizetype realizetype = new Realizetype();
//调用Realizetype类中的clone方法进行对象的克隆
Realizetype clone = realizetype.clone();
System.out.println("原型对象和克隆出来的是否是同一个对象?" + (realizetype == clone));
}
}
package prototype.demo;
/**
* @version v1.0
* @ClassName: Realizetype
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具体的原型对象创建完成!");
}
@Override
public Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
运行结果:clone 原型对象的时候,不会调用构造方法,因为该对象不是通过 new 创建的
具体的原型对象创建完成!
具体原型复制成功!
原型对象和克隆出来的是否是同一个对象?false
2.3.3 案例
【例】使用原型模式生成 “三好学生” 奖状。
同一学校的 “三好学生” 奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后修改奖状上的名字即可。
demo实现
package prototype.test;
/**
* @version v1.0
* @ClassName: Student
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Student {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
package prototype.test;
/**
* @version v1.0
* @ClassName: Citation
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Citation implements Cloneable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
package prototype.test;
/**
* @version v1.0
* @ClassName: CitaionTest
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CitaionTest {
public static void main(String[] args) throws CloneNotSupportedException {
//1,创建原型对象
Citation citation = new Citation();
//创建张三学生对象
Student stu = new Student();
stu.setName("张三");
citation.setStu(stu);
//2,克隆奖状对象
Citation citation1 = citation.clone();
Student stu1 = citation1.getStu();
stu1.setName("李四");
/*citation.setName("张三");
citation1.setName("李四");*/
//3,调用show方法展示
citation.show();
citation1.show();
}
}
package prototype.otest;
/**
* @version v1.0
* @ClassName: Citation
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Citation implements Cloneable {
//三号学生上的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println(getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
package prototype.otest;
/**
* @version v1.0
* @ClassName: CitaionTest
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CitaionTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("张三");
// 复制奖状
Citation c2 = c1.clone();
c2.setName("李四"); // 将奖状的名字改为李四
c1.show();
c2.show();
}
}
2.3.4 浅克隆导致对象没改变
package prototype.test;
/**
* @version v1.0
* @ClassName: Student
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Student {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
package prototype.test;
/**
* @version v1.0
* @ClassName: Citation
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Citation implements Cloneable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
package prototype.test;
/**
* @version v1.0
* @ClassName: CitaionTest
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CitaionTest {
public static void main(String[] args) throws CloneNotSupportedException {
//1,创建原型对象
Citation citation = new Citation();
//创建张三学生对象
Student stu = new Student();
stu.setName("张三");
citation.setStu(stu);
//2,克隆奖状对象
Citation citation1 = citation.clone();
Student stu1 = citation1.getStu();
stu1.setName("李四");
/*citation.setName("张三");
citation1.setName("李四");*/
//3,调用show方法展示
citation.show();
citation1.show();
}
}
深克隆
package com.it.pattern.prototype.test;
/**
* @version v1.0
* @ClassName: Citation
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Citation implements Cloneable {
/*//三好学生上的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}*/
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
package com.it.pattern.prototype.test;
/**
* @version v1.0
* @ClassName: Student
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Student {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
package com.it.pattern.prototype.test;
/**
* @version v1.0
* @ClassName: CitaionTest
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CitaionTest {
public static void main(String[] args) throws CloneNotSupportedException {
//1,创建原型对象
Citation citation = new Citation();
//创建张三学生对象
Student stu = new Student();
stu.setName("张三");
citation.setStu(stu);
//2,克隆奖状对象
Citation citation1 = citation.clone();
Student stu1 = citation1.getStu();
stu1.setName("李四");
/*citation.setName("张三");
citation1.setName("李四");*/
//3,调用show方法展示
citation.show();
citation1.show();
}
}
从磁盘进行深克隆
package com.it.pattern.prototype.test1;
import java.io.Serializable;
/**
* @version v1.0
* @ClassName: Student
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Student implements Serializable {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
package com.it.pattern.prototype.test1;
import java.io.Serializable;
/**
* @version v1.0
* @ClassName: Student
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Student implements Serializable {
//学生的姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
package com.it.pattern.prototype.test1;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @version v1.0
* @ClassName: CitaionTest
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class CitaionTest {
public static void main(String[] args) throws Exception {
//1,创建原型对象
Citation citation = new Citation();
//创建张三学生对象
Student stu = new Student();
stu.setName("张三");
citation.setStu(stu);
//创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/robin/a.txt"));
//写对象
oos.writeObject(citation);
//释放资源
oos.close();
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/robin/a.txt"));
//读取对象
Citation citation1 = (Citation) ois.readObject();
//释放资源
ois.close();
Student stu1 = citation1.getStu();
stu1.setName("李四");
citation.show();
citation1.show();
}
}
2.4 建造者模式
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
这个模式适用于:某个对象的构建过程复杂的情况。
将部件的构造与装配分离,由 Builder 负责构造,Director 进行装配,实现了构建和装配的解耦。
不同的构建器,相同的装配,可以做出不同的对象。
相同的构建器,不同的装配顺序,也可以做出不同的对象。
用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
2.4.1 结构
建造者(Builder)模式包含如下角色:
抽象建造者类 Builder:这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类 ConcreteBuilder:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
产品类 Product:要创建的复杂对象。
指挥者类 Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
2.4.2 实例
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产:
车架有碳纤维,铝合金等材质。
车座有橡胶,真皮等材质。
Bike 是产品,包含车架,车座等组件。
Builder 是抽象建造者。
MobikeBuilder 和 OfoBuilder 是具体的建造者。
Director 是指挥者。
package com.it.pattern.builder.demo1;
/**
* @version v1.0
* @ClassName: Bike
* @Description: 产品对象
* @Author:
*/
public class Bike {
private String frame;//车架
private String seat;//车座
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
package com.it.pattern.builder.demo1;
/**
* @version v1.0
* @ClassName: Builder
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public abstract class Builder {
//声明Bike类型的变量,并进行赋值
protected Bike bike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
//构建自行车的方法
public abstract Bike createBike();
}
package com.it.pattern.builder.demo1;
/**
* @version v1.0
* @ClassName: MobileBuilder
* @Description: 具体的构建者,用来构建摩拜单车对象
* @Author:
*/
public class MobileBuilder extends Builder {
public void buildFrame() {
bike.setFrame("碳纤维车架");
}
public void buildSeat() {
bike.setSeat("真皮车座");
}
public Bike createBike() {
return bike;
}
}
package com.it.pattern.builder.demo1;
/**
* @version v1.0
* @ClassName: OfoBuilder
* @Description: ofo单车构建者,用来构建ofo单车
* @Author:
*/
public class OfoBuilder extends Builder {
public void buildFrame() {
bike.setFrame("铝合金车架");
}
public void buildSeat() {
bike.setSeat("橡胶车座");
}
public Bike createBike() {
return bike;
}
}
package com.it.pattern.builder.demo1;
/**
* @version v1.0
* @ClassName: Director
* @Description: 指挥者类
* @Author:
*/
public class Director {
//声明builder类型的变量
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//组装自行车的功能
public Bike construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}
package com.it.pattern.builder.demo1;
/**
* @version v1.0
* @ClassName: Client
* @Description: TODO(一句话描述该类的功能)
* @Author:
*/
public class Client {
public static void main(String[] args) {
//创建指挥者对象
Director director = new Director(new MobileBuilder());
//让指挥者只会组装自行车
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
上面示例是 Builder 模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类。
但是有些情况下可以简化系统结构,可以把指挥者类和抽象建造者进行结合:
/**
* 抽象builder类
*/
public abstract class Builder {
protected Bike bike = new Bike();
// 构建组装自行车所需零件: 车架,车座
public abstract void buildFrame();
public abstract void buildSeat();
// 根据已有零件构建自行车
public abstract Bike createBike();
// 组装,根据上面步骤,构建自己车,可以获取到自行车实例
// 这应当是director的功能,放到builder中
public Bike construct() {
this.buildFrame();
this.BuildSeat();
return this.createBike();
}
}
这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,不符合单一职责原则。
如果 construct() 过于复杂,建议还是封装到 Director 中。
2.4.3 优缺点
优点:
建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险,符合开闭原则。
**缺点:**使用范围有一定的限制。
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。
2.4.3 使用场景
建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但部件间的建造顺序是稳定的。
创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
2.4.4 扩展 - 构建对象
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
重构前
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainboard() {
return mainboard;
}
public void setMainboard(String mainboard) {
this.mainboard = mainboard;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
重构后
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
// 私有构造方法
private Phone(Builder builder) {
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder screen(String screen) {
this.screen = screen;
return this;
}
public Builder memory(String memory) {
this.memory = memory;
return this;
}
public Builder mainboard(String mainboard) {
this.mainboard = mainboard;
return this;
}
// 使用构建者创建Phone对象
public Phone build() {
return new Phone(this);
}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
在客户端中构建对象:使用起来更灵活方便,某种程度上也可以提高开发效率,很多框架都是这样初始化
public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("华硕")
.memory("金士顿")
.screen("三星")
.build();
System.out.println(phone);
}
}
在 Lombok 中只需要使用 @Builder 即可使用构建者模式来构建对象。
2.5 创建型模式对比
2.5.1 工厂方法模式 vs 建造者模式
两者对比:
工厂方法模式注重的是整体对象的创建方式
建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
2.5.2 抽象工厂模式 vs 建造者模式
两者对比:
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
简单的例子:
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,
那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。