目录
概述
软件设计模式,是一套被反复使用,多数人知晓、经过分类编目的、代码设计经验总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们代码设计经验的总结,具有一定的普遍性,可以反复使用。
(一)软件设计原则
在软件开发中,为了提高软件系统的可维护性和可复用型,增强软件的可扩展性和灵活性,程序员要尽量根据6条原则来开发程序
1.开闭原则
对扩展开放,对修改关闭。在程序需要进行扩展时,不能去修改原有的代码,实现一个热插拔的效果,让程序的扩展性好,易于维护和升级。要达到这样的效果,需要使用接口和抽象类,因为抽象灵活性好,适应性广,只要抽象合理,可以基本保持软件架构的稳定。当需求发生改变时,只需要重新添加一个实现类来扩展就可以了。
2.里氏代换原则
任何基类可以出现的地方,子类一定可以出现。子类可以扩展父类的功能,但不能改变父类原有的功能,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类方法。
---子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
---子类可以增加自己特有的方法
---当子类方法重载父类方法时,子类方法的输入参数要比父类更宽松
---当子类的方法实现父类的方法时,方法的后置条件(方法的返回值)要比父类更严格
3.依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
4.接口隔离原则
客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上
根据接口隔离原则进行改进:
5.迪米特法则
(最少知识原则)
只和直接朋友交谈,不跟陌生人说话。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。目的是降低类之间的耦合,提高模块的相对独立性。
其中,朋友指当前对象本身、成员变量、方法参数、当前对象所创建的对象,当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法
特点:
1.从依赖者角度,只依赖应该依赖的对象
2.从被依赖者角度,只暴露应该暴露的方法
运用时的注意事项:
1.在类的划分上,应创建弱耦合的类 有利于实现可复用的目标
2.在类的结构设计上,尽量降低类成员的访问权限
3. 在类的设计上,优先考虑将一个类设置成不变类
4.在对其他类的引用上,将引用其他对象的次数降到最低
5.不暴露类的属性成员,而是提供访问器(get和set方法)
6.谨慎使用序列化功能
6.合成复用原则
尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
通常类的复用分为继承复用和合成复用两种
--继承复用--
优点:简单易实现
缺点:1.破坏了类的封装性(会将父类的实现细节暴露给子类)
2.子类与父类耦合度高。父类任何实现的改变都会导致子类实现的改变
3.限制了复用的灵活性,从父类继承的实现是静态的,在编译时已经确定
--组合或聚合复用--
优点:1.维持了类的封装性
2.耦合度低
3.复用灵活性高。可以在运行时动态进行
(二)创建者模式
创建者模式主要关注怎样创建对象,主要特点是将对象的创建与使用分离
可以降低系统的耦合度,使用者不需要关注对象的创建细节
1.单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了唯一访问其对象的方式,可以直接访问,不需要实例化该类的对象
结构:
单例类 只能创建一个实例的类
访问类 使用单例类
种类:
饿汉式(类加载就会导致实例对象被创建)
懒汉式(类加载不会被创建,首次被使用时创建)
单例模式的实现:
1.饿汉式
--方式1(静态变量方式)--
//饿汉式 静态成员变量的方式
public class Singleton {
//1.构造方法私有化 防止外界创建对象
private Singleton(){}
//在该类中创建该类对象
//当类被加载时,会创建一个对象(只会创建一次)
private static Singleton instance = new Singleton();
//提供一个公共的方式让外界访问该对象 (静态方法)
public static Singleton getInstance(){
return instance;
}
}
--方式2(静态代码块创建实例)--
//饿汉式 在静态代码块中创建对象
public class Singleton {
//私有化构造方法
private Singleton(){}
//声明
private static Singleton instance;
//在静态代码快中创建对象
static {
instance = new Singleton();
}
//提供一个静态public方法返回该对象
public static Singleton getInstance(){
return instance;
}
}
--饿汉式缺点:只要类加载,就会被创建,如果被创建的实例一直不使用会造成资源的浪费
2.懒汉式
--方式1(线程不安全)--
//懒汉式 方式1 (线程不安全)
public class Singleton {
//构造方法私有化
private Singleton(){}
//声明Singleton类型的变量
private static Singleton instance;
//对外提供访问的方式
public static Singleton getInstance(){
//如果instance为null,创建一个对象,然后返回
//不为null,直接返回
if (instance == null){
instance = new Singleton();
}
return instance;
}
//使用这种方式,在多线程的情况下,如果两个线程同时调用getInstance()方法
//线程1拿到cpu执行权,判断为null,然后进入等待(此时对象未被创建)
//线程2进来后进行判断,此时instance仍为null
//这两个线程都会创建一次对象实例
}
--方式2(线程安全)--
加synchronized关键字
/饿汉式 方式2(添加synchronized关键字)
public class Singleton {
//私有化构造方法
private Singleton(){}
//声明
private static Singleton instance;
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
--方式3(双重检查锁 )--
//饿汉式 方式3(双重检查锁)
//对于getInstance()方法来说绝大部分操作是读操作,
//读操作本来就是线程安全的,没必要让每个线程必须持有锁才能调用该方法
//于是就有了新的实现模式--双重检查锁模式
public class Singleton {
//私有化构造方法
private Singleton(){}
//声明Singleton类型变量
private static Singleton instance;
//对外提供的获取该对象实例的方法
public static synchronized Singleton getInstance(){
//第一次判断,如果instance值不为null,直接返回对象
//如果为null,抢锁 然后在判断
if(instance == null){
synchronized (Singleton.class){
//再次判断,为null创建一个
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁是一种很好的单例实现模式,但是上面这种实现方式存在问题,在多线程的情况下可能会出现空指针的问题。要解决这个问题只需要使用volatile关键字,volatile保证可见性与有序性
//用volatile修饰instance
private static volatile Singleton instance;
添加了volatil关键字后的双重检查锁模式是一种比较好的实现单例模式的一种方式,推荐使用
--方法4(静态内部类方式 也是一种推荐使用的方式)--
//饿汉式 (静态内部类方式)
//静态内部类单例模式中实例有内部类创建,由于JVM在加载外部类的过程中,
//是不会加载内部类的,只有内部类的属性或方法被调用时才会被加载,
//并初始化该内部类的静态属性
public class Singleton {
//私有化构造方法
private Singleton(){}
//定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并初始化外部类对象
private static final Singleton INSTANCE = new Singleton();
}
//提供公共的访问方式
public static Singleton getInstance(){
//调用内部类的属性,内部类被加载
return SingletonHolder.INSTANCE;
}
}
第一次加载Singleton时内部类不会被加载,对象也不会被创建。当第一次调用getInstance()方法时,内部类才会加载。这样不仅能确保线程安全,也能确保Singleton类的唯一性。在没有加锁的情况下,保证了线程安全,并且没有任何性能影响和空间的浪费,是一种优秀的单例模式。
--方式5(枚举方式 极力推荐的单例实现模式 属于饿汉方式)
枚举线程安全,并且只会加载一次,设计者重复利用了枚举的这个特性来实现单例模式。
枚举类型是所有单例模式中唯一一种不会被破坏的单例实现方式
//属于饿汉式,在不考虑浪费空间的情况下首选枚举
public enum Singleton {
INSTANCE;
}
单例模式存在的问题:
----破坏单例模式:
通过序列化或反射可以创建多个Singleton对象实例(通过枚举方式实现的单例模式除外),
单例模式可以被破坏
(1)序列化和反序列化
public class Client {
//
public static void main(String[] args) throws IOException, ClassNotFoundException {
// writeObject();
readObject();
readObject();
}
//向文件中写数据(对象)
public static void writeObject() throws IOException {
//获取Singleton对象 (这个对象已经实现了序列化)
Singleton instance = Singleton.getInstance();
//创建对象输出流对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream("D:\\a.txt"));
//写对象
objectOutputStream.writeObject(instance);
//释放资源
objectOutputStream.close();
}
//从文件中读取数据(对象)
public static void readObject() throws IOException, ClassNotFoundException {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
//读取对象
Singleton instance = (Singleton) ois.readObject();
//释放资源
ois.close();
//打印地址
System.out.println(instance);
}
}
序列化一个Singleton类到文件中,可以通过反序列化获取多个该Singleton类的对象实例
----反序列化 会调用newInstance()方法,通过反射的方式调用无参构造新建一个对象
(2)通过反射破坏单例模式
通过反射可以取消访问检查,通过无参构造器创建多个Singleton类的实例
public class Client {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Singleton的字节码对象
Class clazz = Singleton.class;
//获取无参构造对象
Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建Singleton对象
Singleton s1 = (Singleton)constructor.newInstance();
Singleton s2 = (Singleton)constructor.newInstance();
//判断这两个是否是同一个对象
System.out.println(s1 == s2);//打印false
}
}
问题的解决
1.序列化、反序列化破坏单例模式的解决方法
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
//通过静态内部类实现单例模式
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;
}
//在Singleton类中加一个readResolve()方法来解决破坏单例模式的问题
//当进行反序列化时,将自动调用该方法,
public Object readResolve() {
// 将该方法的返回值直接返回
return SingletonHolder.INSTANCE;
}
}
调用ObjectInputStream的readObject()方法,内部会调用一个hasReadResolveMethod的方法,判断这个类是否有readResolve()方法,有的话就会调用这个方法
2.反射方式破坏单例模式解决方案
反射可以通过Singleton类的无参构造方法创建类,可以在无参构造方法中抛出异常,打断对象的创建(如果该instance为null运行创建,不为null抛出异常)
//对构造器进行修改
private Singleton(){
synchronized (Singleton.class){
if(instance != null){
throw new RuntimeException("不能创建多个对象");
}
}
}
这种方法的前提是instance已经被创建出来,如果通过反射newInstance(),仍能创建多个不同的实例 。
---------------------------------------------------------------------------------------------------------------------------
通过静态内部类实现单例模式,没法直接判断内部类中的对象是否为null,可以在外部类定义一个boolean类型的静态成员变量,判断该类的无参构造器有没有被访问过 (如下)
private static boolean flag = false;
//用同步代码块防止出现多个线程同时进入的情况
private Singleton(){
synchronized (Singleton.class){
//如果flag为true,说明无参构造器被使用过
if(flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
用这种方法 不会出现上面那种方法的问题,但是仍能通过修改flag的值进行破解
用枚举类可以彻底解决单例模式的破坏问题
2.工厂方法模式
如果创建对象时都直接new对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都要进行修改,这显然违背了开闭原则。如果我们使用工厂来生产对象,我没就只要和工厂打交道就可以了,彻底和对象解耦合,如果要更换对象,直接在工厂中更换即可。工厂模式最大的优点:解耦
(1)简单工厂模式(不属于GOF的23中经典设计模式)
简单工厂不是一种设计模式,更像一种编程习惯
简单工厂包括如下角色:
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
具体产品:实现或继承抽象产品的子类
具体功能:提供创建产品的功能,调用者通过该方法创建产品
使用步骤:
- 创建抽象产品类 & 定义具体产品的公共接口;
- 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
- 创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例;
- 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
具体例子:
//1.创建抽象类
abstract class Product{
public abstract void show();
}
//2.继承抽象类
class ProductA extends Product{
@Override
public void show() {
System.out.println("我是A");
}
}
class ProductB extends Product{
@Override
public void show() {
System.out.println("我是B");
}
}
//创建工厂类
public class Factory{
//调用Factory的静态方法 传入一个参数,
// 根据参数返回所需的实例化对象
public static Product getProduct(String type){
if(type.equals("A")){
return new ProductA();
}else if(type.equals("B")){
return new ProductB();
}else{
throw new RuntimeException("没有这个产品");
}
}
}
测试:
public static void main(String[] args) {
//调用静态方法,传入参数就可以获得相应的对象
//使用者不用管具体的创建过程,非常方便
Product productA = Factory.getProduct("A");
Product productB = Factory.getProduct("B");
productA.show();
productB.show();
}
----简单工厂模式优点:
封装了对象的创建过程,可以通过参数直接获得对象。如果要实现新的产品可以直接修改工厂类,降低了客户代码修改的可能性,更加容易扩展
----缺点:
要增加新的产品需要对工厂类进行修改,没有遵循开闭原则;
实例通过工厂类创建,如果工厂无法工作,整个系统都会受到影响;
使用了静态方法,静态方法不能被重写和继承,会造成工厂角色无法形成基于继承的等级结构
(2)工厂方法模式
使用工厂方法模式,完全遵循开闭原则
概念:
定义一个用于创建对象的接口,让子类觉得实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类
结构:
抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品
具体工厂:主要是实现抽象工厂的抽象方法,完成具体产品的创建
抽象产品:定义产品规范,描述产品的主要特性和功能
具体产品:实现了抽象产品角色所定义的接口,有具体工厂来创建,同具体工厂之间一一对应
实现:
//1.创建抽象类
abstract class Product{
public abstract void show();
}
//2.继承抽象类 产品A
public class ProductA extends Product {
@Override
public void show() {
System.out.println("我是A");
}
}
//产品B
public class ProductB extends Product {
@Override
public void show() {
System.out.println("我是B");
}
}
//工厂接口
public interface Factory {
Product createProduct();
}
//工厂A,实现工厂接口,用来制造产品A
public class FactoryA implements Factory{
public Product createProduct() {
return new ProductA();
}
}
//工厂B,实现工厂接口,用来制造产品B
public class FactoryB implements Factory{
public Product createProduct() {
return new ProductA();
}
}
测试:
public class Client {
public static void main(String[] args) {
//要获得产品A,只需要知道工厂名,通过工厂制作产品
Factory factoryA = new FactoryA();
Product productA = factoryA.createProduct();
Factory factoryB = new FactoryB();
Product productB = factoryB.createProduct();
}
}
通过肉眼观察,可以发现上面这种写法看起来更麻烦,每个产品都对应一个工厂。
工厂模式适用于需要生成复杂对象的地方,而简单的对象,特别是只需要通过new就可以完成创建的对象,无须使用工厂模式。
优点:
一个调用者想创建一个对象,只要知道其名称就可以了。
扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每次增加产品,都需要增加一个具体类和对象实现工厂,增加了系统的复杂度
3.抽象工厂模式
工厂方法模式只生成同种类产品,会导致系统中存在大量的工厂类,增加系统的开销。此时,可以考虑将一些相关产品组成一个产品族,由同一个工厂统一生产,这就是抽象工厂模式的基本思想
--- 将同一个具体工厂生产的位于不同等级的一组产品称为一个产品族
概念:
是一种为访问量提供一个创建一组相关或相关依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同产品的模式结构
结构:
抽象工厂:提供创建产品的接口,包含多个创建产品的方法,可以创建不同等级的产品
具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建
抽象产品:定义产品规范 (抽象工厂模式有多个抽象产品)
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,同具体工厂是多对一关系
实现:
//---定义多个不同等级的抽象类---
//这是两轮车(wheel:轮子,车轮)
public abstract class TwoWheelCar {
abstract void show();
}
//这是三轮车
public abstract class ThreeWheelCar {
abstract void show();
}
//---创建工厂接口,提供制造两轮车和三轮车的方法---
public interface Factory {
abstract TwoWheelCar createTwoWheelCar();
abstract ThreeWheelCar createThreeWheelCar();
}
//然后创建具体产品(继承两个抽象类)
//创建具体工厂实现工厂接口
//通过具体的工厂制造不同等级的产品
抽象工厂模式的优缺点
优点:当一个产品族中多个对象被设计成一起工作时,他能保证客户端始终只使用同一个产品族中的对象
缺点:当产品族中需要添加一个新的产品时(添加新的等级),所有工厂类都需要进行修改
使用场景:
当需要创建的对象是一系列相互关联或相互依赖的产品族时
系统中有多个产品族,但每次 只使用其中的某一族产品
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构
--模式扩展--
简单工厂+配置文件接触耦合
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,通过反射创建对象进行存储,客户端如果需要对象,可以根据名字直接进行获取。
public class CoffeeFactory {
//加载配置文件,获取配置文件中配置的全类名,并创建该类对象进行存储
//1。定义容器对象存储咖啡对象
private static HashMap<String,Coffee> map = new HashMap<String, Coffee>();
//2.加载配置文件
//获取名字和类的全限定名
//通过反射创建实例,放入map中
static {
//创建properties对象
Properties properties = new Properties();
//调用properties对象中的load方法进行配置文件的加载
InputStream resourceAsStream = CoffeeFactory.class.getClassLoader().getResourceAsStream(
"bean.properties");
try {
properties.load(resourceAsStream);
//从properties集合中获取全类名并创建
Set<Object> keys = properties.keySet();
for (Object key : keys){
String className = properties.getProperty((String) key);
//通过反射创建对象
Class clazz = Class.forName(className);
Coffee coffee = (Coffee)clazz.newInstance();
//将名称和对象存入容器中
map.put((String)key, coffee);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
//调用这个方法获取Coffee对象
public static Coffee createCoffee(String name){
return map.get(name);
}
}
4.原型模式
概述:
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象
结构:
-
抽象原型类:规定了具体原型对象必须实现的clone()方法,是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口、抽象类甚至是一个具体的实现
-
具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象,在克隆方法中返回一个自己的克隆对象
-
访问类:使用具体原型类中的clone()方法来复制新的对象
--原型模式的克隆分为浅克隆和深克隆
浅克隆:创建一个新对象,新对象的属性与原对象完全相同,对于非基本数据类型,仍指向原有属性所指向的对象的内存空间
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有对象地址
Java中的Object类中提供中提供了clone()方法来实现浅克隆,Cloneable接口就是上面的抽象原型类,实现这个接口的就是具体原型类
//实现接口,重写clone()方法
public class Realizetype implements Serializable {
private Student student;
@Override
//clone()在父类中的访问修饰符是protected,覆盖这个方法时,
//可以把访问修饰符改为public,让其他包的类也能克隆
public Realizetype clone() throws CloneNotSupportedException {
System.out.println("复制");
return (Realizetype)super.clone();
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
上面这种方法是浅克隆,克隆对象的student只是拷贝了原对象的引用,没有拷贝原对象的student的具体内容,克隆对象和原对象的student是同一个人,如果对克隆对象的student的属性进行修改,会影响原对象的student
使用场景:
对象的创建非常复杂,可以使用原型模式快速创建
性能和安全要求比较高
扩展(深克隆)
方法1: 嵌套使用克隆
//实现接口,重写clone()方法
public class Realizetype implements Cloneable{
//此时Student类也实现了Cloneable接口
//这个Student类中的属性只有基本数据类型和String
private Student student;
@Override
public Realizetype clone() throws CloneNotSupportedException {
System.out.println("复制");
//创建一个克隆对象
Realizetype clone = (Realizetype)super.clone();
//克隆一个student
clone.student = (Student) clone.student.clone();
return clone;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
方法2:序列化实现深克隆
public class Client {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
//创建一个原对象
Realizetype realizetype = new Realizetype();
realizetype.setStudent(new Student("jack",12));
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream(new File("D:\\a.txt")));
objectOutputStream.writeObject(realizetype);
objectOutputStream.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(new File("D:\\a.txt")));
Realizetype o = (Realizetype)ois.readObject();
ois.close();
//测试结果是否为统一对象(结果均为false)
System.out.println(o == realizetype);
System.out.println(o.getStudent() == realizetype.getStudent());
}
}
5.建造者模式
概述:
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同表示。将一个对象分为多个简单的对象,一步一步构建而成。
主要角色:
-
产品角色:是包含多个组件的复杂对象,由具体建造者来创建其零件
-
抽象建造者:是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法
-
具体建造者:实现抽象建造者接口,完成复杂产品各个部件的具体创建方法
-
指挥者:调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息
例子:
//1.产品(由多个组件组成)
//2.抽象建造者
public abstract class Builder {
//内部包含创建产品各个部件的抽象方法
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品的方法
public abstract Product build();
}
//3.具体建造者 通过这个类创建具体的部件
public class ConcreteBuilder extends Builder{
protected Product product = new Product();
//下面的代码表示product的组件的构造过程
@Override
public void buildPartA() {
System.out.println("A组件构建。。。");
}
@Override
public void buildPartB() {
System.out.println("B组件构建。。。");
}
@Override
public void buildPartC() {
System.out.println("C组件构建。。。");
}
@Override
public Product build() {
return product;
}
}
//4.指挥者 调用建造者的方法来完成对象的创建
public class Director {
private Builder builder;
//指挥者的构造器传入一个Builder的实现子类
public Director(Builder builder){
this.builder = builder;
}
//调用方法创建,指挥者只负责调用方法
//具体创建过程由Builder完成
public Product buildProduct(){
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.build();
}
}
//测试
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Product product = new Director(builder).buildProduct();
}
}
优点:
-
建造者模式封装性很好使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的。因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性
-
在建造者模式中,客户端不必知道产品内部组成细节,将产品本身与产品的建造过程解耦,使得相同的创建过程可以创建不同的产品对象
-
可以更加精细地控制产品的创建过程
-
很容易进行扩展,如果有需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前的代码
缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间差异性很大,则不适合建造者模式,因此其使用规范受到一定的限制
使用场景:
--创建的对象复杂,由多个部件组成,各部件面临着复杂的变化,但构建间的建造顺序是稳定的
--创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式
扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,创建这个类的实例,代码可读性会很差,此时可以用建造者模式进行重构
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;
//可以通过返回的Builder对象继续调用方法添加组件(链式编程)
return this; //这里的this指Builder
}
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;
}
//先通过Builder创建各个组件,再将Builder作为参数传入Phone的
// 构造器中,创建一个Phone
public Phone build(){
return new Phone(this);
}
}
}
//测试
public class Client {
public static void main(String[] args) {
//创建一个Builder,通过Builder来创建手机
//构建的顺序由使用者来决定
Phone phone = new Phone.Builder()
.cpu("cpu")
.memory("内存条")
.screen("屏幕")
.mainBoard("主板")
.build();
System.out.println(phone);
}
}
工厂方法模式与建造者模式对比
工厂方法模式注重的是整体对象的创建方式 ;而建造者模式注重的是部件构建的过程
抽象工厂模式与建造者模式对比
抽象工厂模式实现对产品家族的创建,采取抽象工厂模式不需要关系构建过程,只关心产品由什么工厂生产即可。
建造者模式则要求按照指定的蓝图建造产品
(三)结构型模式
(四)行为型模式