【设计模式】创建者模式(单例工厂原型建造者)
文章目录
- 1.单例设计模式
- 1.1 单例模式概述
- 1.2 单例模式的实现
- 1.2.1 饿汉式(静态变量方式)
- 1.2.2 饿汉式(静态代码块方式)
- 1.2.3 懒汉式(线程不安全)
- 1.2.4 懒汉式(双重检查锁)
- 1.2.5 懒汉式(静态内部类)
- 1.2.6 枚举方式
- 1.3 存在的问题
- 1.4 JDK单例模式的体现
- 2.工厂模式
- 2.1 工厂模式概述
- 2.2 简单工厂模式
- 2.2.1 结构
- 2.2.2 实现
- 2.2.4 优缺点
- 2.2.5 静态工厂
- 2.3 工厂方法模式
- 2.3.1 概念
- 2.3.2 结构
- 2.3.3 实现
- 2.3.4 优缺点
- 2.4 抽象工厂模式
- 2.4.1 概念
- 2.4.2 结构
- 2.4.3 实现
- 2.4.4 优缺点
- 2.4.5 使用场景
- 2.5 模式扩展(简单工厂+配置文件解除耦合)
- 2.6 JDK源码解析-Collection.iterator方法
- 3.原型模式
- 3.1 概述
- 3.2 结构
- 3.3 实现
- 3.4 案例
- 3.5 使用场景
- 3.6 深克隆
- 4.建造者模式
- 4.1 概述
- 4.2 结构
- 4.3 实例
- 4.4 优缺点
- 4.5 使用场景
- 4.6 模式扩展
- 5.创建者模式对比
- 5.1 工厂方法模式 VS 建造者模式
- 5.2 抽象工厂模式 VS 建造者模式
序言:
创建型模式的主要关注点是
怎样创建对象?
,它的主要特点是将对象的创建与使用分离
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
创建型模式分为:
- 单例模式
- 工厂模式
- 原型模式
- 建造者模式
1.单例设计模式
1.1 单例模式概述
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的主要有以下角色:
单例类
:只能创建一个实例的类访问类
:使用单例类
为什么要使用单例模式?
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。因此这里需要用到单例模式
使用单例模式的好处?
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间
1.2 单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
1.2.1 饿汉式(静态变量方式)
public class Singleton {//私有构造方法private Singleton() {}//在成员位置创建该类的对象private static Singleton instance = new Singleton();//对外提供静态方法获取该对象public static Singleton getInstance() {return instance;}
}
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
1.2.2 饿汉式(静态代码块方式)
public class Singleton {//私有构造方法private Singleton() {}//在成员位置创建该类的对象private static Singleton instance;static {instance = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return instance;}
}
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的静态变量方式基本上一样,当然该方式也存在内存浪费问题。
1.2.3 懒汉式(线程不安全)
public class Singleton {//私有构造方法private Singleton() {}//在成员位置创建该类的对象private static Singleton instance;//对外提供静态方法获取该对象public static Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;}
}
从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
1.2.4 懒汉式(双重检查锁)
public class Singleton4 {//构造私有private Singleton4() {System.out.println("private Singleton4()");}//唯一实例//这里volatile的作用是保证共享变量有序性!private static volatile Singleton4 INSTANCE = null;//双检锁优化public static Singleton4 getInstance() {//实例没创建,才会进入内部的 synchronized 代码块,提高性能,防止每次都加锁if (INSTANCE == null) {//可能第一个线程在synchronized 代码块还没创建完对象时,第二个线程已经到了这一步,所以里面还需要加上判断synchronized (Singleton4.class) {//也许有其他线程已经创建实例,所以再判断一次if (INSTANCE == null) {INSTANCE = new Singleton4();}}}return INSTANCE;}
}
1.2.5 懒汉式(静态内部类)
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton {//私有构造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
- 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
- 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
1.2.6 枚举方式
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。(枚举属于饿汉式方式)
public enum Singleton{INSTANCE;//枚举的构造方法默认是private的,可以不写Singleton() {}//获得实例方法(这个可以不要,枚举变量都是public的)public static Singleton getInstance() {return INSTANCE;}
}
1.3 存在的问题
看这里→单例模式详解
1.4 JDK单例模式的体现
看这里→单例模式详解
2.工厂模式
2.1 工厂模式概述
这里我们用一个案例来了解工厂模式。
需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
具体类的设计如下:
咖啡类:
public abstract class Coffee {public abstract String getName();//加糖public void addSugar(){System.out.println("加糖");}//加奶public void addMilk(){System.out.println("加奶");}
}
美式咖啡:
public class LatteCoffee extends Coffee{@Overridepublic String getName() {return "拿铁咖啡";}
}
拿铁咖啡:
public class AmericanCoffee extends Coffee{@Overridepublic String getName() {return "美式咖啡";}
}
咖啡店类:
public class CoffeeStore {public Coffee orderCoffee(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("对不起,你所点的coffee没有!");}//加配料coffee.addMilk();coffee.addSugar();return coffee;}
}
可以看出如果我们要添加新的咖啡种类,就必须修改咖啡店类,违背了开闭原则!
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。
如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的
所以说,工厂模式最大的优点就是:解耦
2.2 简单工厂模式
2.2.1 结构
简单工厂包含如下角色:
抽象产品
:定义了产品的规范,描述了产品的主要特性和功能。具体产品
:实现或者继承抽象产品的子类具体工厂
:提供了创建产品的方法,调用者通过该方法来获取产品。
2.2.2 实现
现在使用简单工厂对上面案例进行改进,类图如下:
工厂类代码如下:
public class SimpleCoffeeFactory {public Coffee createCoffee(String type) {Coffee coffee = null;if ("american".equals(type)) {coffee = new AmericanCoffee();} else if ("latte".equals(type)) {coffee = new LatteCoffee();} else {throw new RuntimeException("对不起,您所点的咖啡没有!");}return coffee;}
}
咖啡店类代码修改:
public class CoffeeStore {public Coffee orderCoffee(String type) {SimpleCoffeeFactory factory = new SimpleCoffeeFactory();Coffee coffee = factory.createCoffee(type);//加配料coffee.addMilk();coffee.addSugar();return coffee;}
}
测试:
public class Client {public static void main(String[] args) {//创建咖啡店类对象CoffeeStore store=new CoffeeStore();Coffee coffee = store.orderCoffee("latte");System.out.println(coffee.getName());}
}
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。
这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
2.2.4 优缺点
优点:
- 封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
- 增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
2.2.5 静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。代码如下:
public class SimpleCoffeeFactory {public static Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffe;}
}
咖啡店类修改:
public class CoffeeStore {public Coffee orderCoffee(String type) {Coffee coffee = SimpleCoffeeFactory.createCoffee(type);//加配料coffee.addMilk();coffee.addSugar();return coffee;}
}
2.3 工厂方法模式
2.3.1 概念
工厂方法模式:
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
2.3.2 结构
工厂方法模式的主要角色:
抽象工厂
:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。具体工厂
:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。抽象产品
:定义了产品的规范,描述了产品的主要特性和功能。具体产品
:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
2.3.3 实现
使用工厂方法模式对上例进行改进,类图如下:
抽象工厂:
public interface CoffeeFactory {Coffee createCoffee();
}
具体工厂:
public class LatteCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new LatteCoffee();}
}public class AmericanCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new AmericanCoffee();}
}
咖啡店类:
public class CoffeeStore {private CoffeeFactory coffeeFactory;public void setCoffeeFactory(CoffeeFactory coffeeFactory){this.coffeeFactory=coffeeFactory;}//点咖啡功能public Coffee orderCoffee() {Coffee coffee = coffeeFactory.createCoffee();coffee.addMilk();coffee.addSugar();return coffee;}
}
测试:
public class Client {public static void main(String[] args) {//创建咖啡店类对象CoffeeStore store=new CoffeeStore();CoffeeFactory factory=new AmericanCoffeeFactory();store.setCoffeeFactory(factory);Coffee coffee = store.orderCoffee();System.out.println(coffee.getName());}
}
从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
2.3.4 优缺点
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度
2.4 抽象工厂模式
2.4.1 概念
抽象工厂模式:
- 是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
- 抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族。
下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。
2.4.2 结构
抽象工厂模式的主要角色如下:
抽象工厂
:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。具体工厂
:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。抽象产品
:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。具体产品
:实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
2.4.3 实现
- 现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等。
- 如果要按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。
- 其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。
- 所以这个案例可以使用抽象工厂模式实现。
类图如下:
甜品抽象类:
public abstract class Dessert {public abstract void show();
}
甜品实现类:
public class Trimisu extends Dessert{@Overridepublic void show() {System.out.println("提拉米苏");}
}
public class MatchaMoousse extends Dessert{@Overridepublic void show() {System.out.println("抹茶慕斯");}
}
抽象工厂:
public interface DessertFactory {Coffee createCoffee();Dessert createDessert();
}
具体工厂:
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {public Coffee createCoffee() {return new AmericanCoffee();}public Dessert createDessert() {return new MatchaMousse();}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {public Coffee createCoffee() {return new LatteCoffee();}public Dessert createDessert() {return new Tiramisu();}
}
测试:
public class Client {public static void main(String[] args) {ItalyDessertFactory factory=new ItalyDessertFactory();Coffee coffee = factory.createCoffee();Dessert dessert = factory.createDessert();coffee.addMilk();coffee.addSugar();System.out.println(coffee.getName());dessert.show();}
}
如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。
2.4.4 优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
2.4.5 使用场景
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
2.5 模式扩展(简单工厂+配置文件解除耦合)
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
第一步:定义配置文件
为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties
american=com.xpp.pattern.factory.configFactory.AmericanCoffee
latte=com.xpp.pattern.factory.configFactory.LatteCoffee
第二步:改进工厂类
public class CoffeeFactory {//加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储//1.定义容器对象存储咖啡对象private static Map<String, Coffee> map = new HashMap<String, Coffee>();//2.加载配置文件,只需要加载一次static {Properties p = new Properties();InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");try {p.load(is);Set<Object> keys = p.keySet();for (Object key : keys) {String className = p.getProperty((String) key);//通过反射创建对象Class aClass = Class.forName(className);Coffee coffee = (Coffee) aClass.newInstance();//将名称和对象存储到容器中map.put((String) key, coffee);}} catch (Exception e) {e.printStackTrace();}}public static Coffee createCoffee(String name) {return map.get(name);}
}
测试:
public class Client {public static void main(String[] args) {Coffee latte = CoffeeFactory.createCoffee("latte");System.out.println(latte.getName());}
}
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。
2.6 JDK源码解析-Collection.iterator方法
public class Demo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("令狐冲");list.add("风清扬");list.add("任我行");//获取迭代器对象Iterator<String> it = list.iterator();//使用迭代器遍历while(it.hasNext()) {String ele = it.next();System.out.println(ele);}}
}
对上面的代码大家应该很熟,使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。我们看通过类图看看结构:
- Collection接口是抽象工厂类
- ArrayList是具体的工厂类
- Iterator接口是抽象商品类
- ArrayList类中的Iter内部类是具体的商品类
- 在具体的工厂类中iterator()方法创建具体的商品类的对象
除此之外:
- DateForamt类中的getInstance()方法使用的是工厂模式
- Calendar类中的getInstance()方法使用的是工厂模式
3.原型模式
3.1 概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
3.2 结构
原型模式包含如下角色:
抽象原型类
:规定了具体原型对象必须实现的的 clone() 方法。具体原型类
:实现抽象原型类的 clone() 方法,它是可被复制的对象。访问类
:使用具体原型类中的 clone() 方法来复制新的对象。
类图如下:
3.3 实现
原型模式的克隆分为浅克隆和深克隆:
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了 clone()
方法来实现浅克隆。 Cloneable
接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:
Realizetype(具体的原型类):
public class Realizetype implements Cloneable{public Realizetype(){System.out.println("具体的原型对象创建完成!");}@Overrideprotected Realizetype clone() throws CloneNotSupportedException {System.out.println("具体原型复制成功!");return (Realizetype) super.clone();}
}
PrototypeTest(测试访问类):
public class PrototypeTest {public static void main(String[] args) throws CloneNotSupportedException {Realizetype r1 = new Realizetype();Realizetype r2 = r1.clone();System.out.println("对象r1和r2是同一个对象?" + (r1 == r2));//false}
}
3.4 案例
用原型模式生成“三好学生”奖状
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
类图如下:
代码如下:
public class Citation implements Cloneable {//三好学生的姓名private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}void show() {System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");}@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();}
}
测试:
public class CitaionTest {public static void main(String[] args) throws CloneNotSupportedException {//1.创建原型对象Citation citation=new Citation();//2.克隆奖状对象Citation citation1 = citation.clone();citation.setName("张三");citation1.setName("李四");//3.调用show方法展示citation.show();citation1.show();}
}
输出结果:
张三同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
3.5 使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。
3.6 深克隆
将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下:
public class Citation implements Cloneable {//三好学生的姓名private Student student;public Student getStudent() {return student;}public void setStudent(Student student) {this.student = student;}void show() {System.out.println(student.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");}@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();}
}
Student类:
public class Student {private String name;//省略get,set,toString方法
}
测试:
public class CitaionTest {public static void main(String[] args) throws CloneNotSupportedException {//1.创建原型对象Citation citation = new Citation();Student student = new Student();student.setName("张三");citation.setStudent(student);//2.克隆奖状对象Citation citation1 = citation.clone();Student student1 = citation1.getStudent();student1.setName("李四");//3.调用show方法展示citation.show();citation1.show();}
}
输出结果:
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
student对象和student1对象是同一个对象,就会产生将student1对象中name属性值改为“李四”,两个Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。
代码如下:
public class CitaionTest {public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {//1.创建原型对象Citation citation = new Citation();Student student = new Student();student.setName("张三");citation.setStudent(student);//创建对象输出流对象ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:/a.txt"));//写对象oos.writeObject(citation);//释放资源oos.close();//创建对象输入流对象ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:/a.txt"));//读取对象Citation citation1 = (Citation) ois.readObject();//释放资源ois.close();citation1.getStudent().setName("李四");citation1.show();citation.show();}
}
输出结果:
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
张三同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
**注意:**Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。
4.建造者模式
4.1 概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
- 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
- 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
4.2 结构
建造者(Builder)模式包含如下角色:
抽象建造者类
:这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。具体建造者类
:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。产品类
:要创建的复杂对象。指挥者类
:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
类图如下:
4.3 实例
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:
产品类:
public class Bike {//车架private String frame;//车座private String seat;//get,set省略
}
抽象建造者类:
public abstract class Builder {//说明Bike类型,并进行赋值protected Bike bike = new Bike();public abstract void builderFrame();public abstract void builderSeat();public abstract Bike createBike();}
具体建造者类:
//摩拜单车建造类
public class MobileBuilder extends Builder {@Overridepublic void builderFrame() {bike.setFrame("碳纤维车架");}@Overridepublic void builderSeat() {bike.setSeat("真皮车座");}@Overridepublic Bike createBike() {return bike;}
}//Ofo单车建造类
public class OfoBuilder extends Builder {@Overridepublic void builderFrame() {bike.setFrame("铝合金车架");}@Overridepublic void builderSeat() {bike.setSeat("橡胶车座");}@Overridepublic Bike createBike() {return bike;}
}
指挥者类:
public class Director {//声明builder类型的变量private Builder builder;public Director(Builder builder){this.builder=builder;}//组装自行车public Bike construct(){builder.builderFrame();builder.builderSeat();return builder.createBike();}
}
测试类:
public class Client {public static void main(String[] args) {//创建指挥者镀锡Director director=new Director(new MobileBuilder());//指挥者指挥组装自行车Bike bike = director.construct();System.out.println(bike.getFrame());}
}
注意:上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类
但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合:
// 抽象 builder 类
public abstract class Builder {protected Bike mBike = new Bike();public abstract void buildFrame();public abstract void buildSeat();public abstract Bike createBike();public Bike construct() {this.buildFrame();this.BuildSeat();return this.createBike();}
}
注意:这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。
4.4 优缺点
优点:
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
4.5 使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
4.6 模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
重构前代码如下:
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;}//省略get,set,toString方法
}
客户端代码:
public class Client {public static void main(String[] args) {//构建Phone对象Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");System.out.println(phone);}
}
上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?
代码的可读性及使用的成本就是比较高
重构后代码:
public class Phone {private String cpu;private String screen;private String memory;private String mainboard;//私有构造方法private Phone(Builder builder) {cpu = builder.cpu;screen = builder.screen;memory = builder.memory;mainboard = builder.mainboard;}public static final class Builder {private String cpu;private String screen;private String memory;private String mainboard;public Builder cpu(String val) {cpu = val;//返回this是为了链式调用return this;}public Builder screen(String val) {screen = val;return this;}public Builder memory(String val) {memory = val;return this;}public Builder mainboard(String val) {mainboard = val;return this;}public Phone build(){//把当前的Builder对象传给Phone构造方法return new Phone(this);}}@Overridepublic 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);}
}
可以看出重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。
5.创建者模式对比
5.1 工厂方法模式 VS 建造者模式
- 工厂方法模式注重的是整体对象的创建方式
- 建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
5.2 抽象工厂模式 VS 建造者模式
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
- 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
【设计模式】创建者模式(单例工厂原型建造者)相关推荐
- 设计模式C#描述——单例与多例模式
设计模式C#描述--单例与多例模式 作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 单例模式有以下特点: 单例类只能有一个实例. 单例 ...
- 设计模式学习笔记——单例(Singleton)模式
设计模式学习笔记--单例(Singleton)模式 @(设计模式)[设计模式, 单例模式, Singleton, 懒汉式, 饿汉式] 设计模式学习笔记单例Singleton模式 基本介绍 单例案例 类 ...
- 重学Java设计模式-创建者模式-工厂方法模式
重学Java设计模式-创建者模式-工厂方法模式 内容摘自:重学 Java 设计模式:实战工厂方法模式「多种类型商品不同接口,统一发奖服务搭建场景」 | bugstack 虫洞栈 工厂方法模式介绍 图片 ...
- 重学Java设计模式-创建者模式-建造者模式
重学Java设计模式-创建者模式-建造者模式 内容摘自:重学 Java 设计模式:实战建造者模式「各项装修物料组合套餐选配场景」 | bugstack 虫洞栈 建造者模式介绍 图片来自:https:/ ...
- 设计模式-创建者模式篇
设计模式 目录: 一.单例模式 二.工厂模式 三.抽象工厂模式 四.原型模式 五.建造者模式 注:学习视频:黑马程序员Java设计模式 创建者模式 创建型模式的主要关注点是"怎样创建对象?& ...
- spring单例的bean是单例还是原型
转载请注明出处:https://blog.csdn.net/qq_27218667/article/details/99690798 有一次老大突然问spring管理的bean是单例还是多例,当时印象 ...
- spring中的单例工厂SingletonBeanRegistry设计与实现
单例工厂接口为SingletonBeanRegistry,主要是单例的注册,其默认实现为DefaultSingletonBeanRegistry 1.类层次图 2.单例工厂在循环依赖时的流程
- 高仿真的类-单例工厂的顶层设计
/*** 单例工厂的顶层设计*/ public interface V1BeanFactory {/*** 根据beanName从IOC容器中获得一个实例Bean* @param beanName* ...
- IoC与DI工厂、单例、原型模式详解
1.工厂模式 1.1 工厂模式的由来 在现实生活中我们都知道 原始社会自给自足(没有工厂) 农耕社会有了小作坊(简单工厂,如民间酒坊) 工业革命后有了流水线(工厂方法,自产自销) 现代产业链中有代工厂 ...
最新文章
- 多场景下的AI疫情防控“天网”:解读云边端联动下的全栈AI技术
- 可视化生信分析利器 Galaxy 之 Docker 开发
- R语言aggregate函数数据聚合实战
- 在世界第二届半机械人奥运会上,瘫痪驾驶员在Cybathlon BCI竞赛中争夺金牌
- Android应用开发SharedPreferences存储数据的使用方法
- for ie无效 in js_关于js中for in的缺陷浅析
- viewer.js实现预览效果
- 修改2440里面的FriendlyARM
- Elasticsearch Painless Script详解
- jmeter web监听结果_Jmeter性能测试
- 高通量数据中批次效应的鉴定和处理(六)- 直接校正表达矩阵
- c语言第六章条件型循环结构,C语言课件(第六章 循环结构)
- mysql 查询排序位置_MySQL-基础查询与排序
- 【c++ templates读书笔记】【4】技巧性基础知识
- Intel HAXM is required to run this AVD.HAXM is not installed.
- 量化:纸上得来终觉浅,绝知此事要躬行。
- 海思3559编译live555
- 那周余嘉熊掌将得队——用户使用调查报告
- 四只头雁:5G to B规模商用的开启节点
- windows 打印机架构