前言 - 熟能生巧(★★★)

  • 关于设计模式的学习,需要结合具体的应用场景进行理解,即站在用户的角度去理解需求,目的是让自己设计的代码能够为用户提供统一的接口,并且设计的模块具有高内聚低耦合、有更好的可扩展性、便于后期的代码维护

  • 设计模式就是根据不同的需求设计出来了容易复用的框架,是工程应用中优质代码,比如观察者模式,工厂模式等,这些都和生活中的典型例子息息相关,既方便理解也方便应用

  • 有些设计模式在实现时有多种变种,比如工厂模式可以细分成简单工厂和多工厂模式,单例模式可以划分成懒汉式和饿汉式,是否需要用,具体用哪个需要结合具体场景考虑。如果功能简单,刻意使用某种设计模式可能会增加模块的复杂度

  • 这些设计模式虽然几乎尽人皆知,但不是每个人都能用得好。比如对于工厂模式,要想孰能生巧,熟练掌握该模式,需要多思考工厂方法如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模板方法模式、单例模式、原型模式等)变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在

  • 关于类图中属性的可见性

    • +(public):可以被所有其他类所访问。
    • (private):只能被自己类访问和修改。
    • #(protected) :自身,子类及同一个包中类可以访问。
    • ~(package)
    • default:同一包中的类可以访问,声明时没有加修饰符
  • Java的特征包括:封装,多态,继承和抽象

    • 为了理解Java的抽象,建议先根据需求,将各个实体类逐层向上抽象(由具体到抽象的过程),进而理解各种设计模式的设计逻辑和思路,分析其封装性、可扩展性、可见性(是否要使用privateprotected或者final修饰)和可维护性如何,是否满足设计模式的六大原则。
    • 接着通过与其他设计模式的比较,从抽象到具体理解该设计模式在需求解决上有哪些优点和缺点,巩固设计模式的学习。
  • 通过的典型类图,即一个接口,多个抽象类,然后是N个实现类

设计模式的六大原则

参考书籍:设计模式之禅

笔记配套代码

文章目录

  • 前言 - 熟能生巧(★★★)
  • 创建型设计模式
    • 一、单例模式
      • 1、需求假设
      • 2、单例模式的定义和实现
        • 1)定义(自行实例化)
        • 2)类图
        • 3)代码实现(饿汉式,线程安全 ★★★★)
      • 3、优点和适用场景
        • 1)优点
        • 2)缺点(★★)
        • 3)应用场景(★★★★)
      • 4、单例模式的扩展
        • 1)懒汉式 - 线程不安全(★★★★)
        • 2)对象复制问题
        • 3)多例模式(应该可以配合抽象工厂实现固定容量的线程池,快速响应提交的任务 ★★★★)
    • 二、工厂模式
      • 1、需求假设(★★★)
      • 2、工厂模式的定义和实现
        • 1)定义(产品单一,★★★★)
        • 2)类图
        • 3)可见性分析
        • 4)代码实现(★★★★★)
          • a)人类接口
          • b)各人种实现类
          • c)人类创造工厂类(★★★★)
          • d)Main类
      • 3、优点和适合的场景
        • 1)优点(★★★★)
        • 2)场景
      • 4、工厂模式的扩展
        • 1)简单(静态)工厂模式
        • 2)多工厂模式(Executors ★★★★)
        • 3)替代单例模式
        • 4)延迟初始化(★★★★)
    • 三、抽象工厂模式
      • 1、需求假设
      • 2、抽象工厂模式的定义和实现
        • 1)定义(产品间具有依赖关系,★★★★)
        • 2)类图(男女人类 -> 黄种人抽象类 -> 接口)
        • 3)可见性分析
        • 4)代码实现
          • a)人种接口
          • b)抽象人种类
          • c)人种实现类
          • d)抽象工厂类(关于每个产品族的抽象)
          • e)工厂实现类(N个产品对应N个工厂实现类)
          • f)Main类
      • 3、优点和适合场景
        • 1)小总结(★★★★★)
        • 2)优缺点(★★★)
          • a)优点
          • b)缺点
        • 3)应用场景(★★★★)
    • 四、生成器模式
      • 1、需求假设
      • 2、生成器模式的定义和实现
        • 1)定义
        • 2)类图(★★★★)
        • 3)可见性分析(★★★)
        • 4)代码实现
          • a)车辆模型抽象类
          • b)奔驰和宝马模型
          • c)Main类生产BMW模型
          • d)抽象汽车组装类(使用建造者管理Main类,为用户提供统一的接口)
          • e)车模型组装者
          • f)导演类
      • 3、优点和适用场景
        • 1)优点
        • 2)应用场景
        • 3)建造者和工厂方法的区别(★★★★★)
    • 五、原型模式
      • 1、需求假设
      • 2、定义和实现
        • 1)浅拷贝
        • 2)深拷贝(★★★★)
      • 3、应用场景(★★★★)
        • 1)优点:
        • 2)应用场景
        • 3)注意事项

创建型设计模式

创建型设计模式的目的是根据用户简单需求,为用户提供创建好的产品,同时为用户隐藏产品的具体创建过程; 用户无法直接new产品,需要通过以下创建型设计模式来获得产品。

一、单例模式

1、需求假设

需求:皇帝每天要上朝接待臣子、处理政务,臣子每天要
叩拜皇帝,皇帝只能有一个。问臣子如何保证每天访问的皇帝是同一个人?

2、单例模式的定义和实现

1)定义(自行实例化)

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

2)类图

单例模式的通用类图

3)代码实现(饿汉式,线程安全 ★★★★)

皇帝类:

public  class Emperor{private static final Emperor emperor = new Emperor();private Emperor(){}public static Emeperor getInstance(){return emperor;}//皇帝发话了public static void say(){System.out.println("我就是皇帝某某某....");}
}

臣子类:

public class Minister {public static void main(String[] args) {for(int day=0;day<3;day++){Emperor emperor=Emperor.getInstance();emperor.say();}//三天见的皇帝都是同一个人,荣幸吧!}
}

Note:皇帝类有两个注意点:

  • 由于单例模式下的实体类构造函数是私有的(private),要想访问到类内初始化的实例对象,需要设置静态方法static
  • 饿汉式单例模式直接在类内初始化实例对象,要想通过静态方法返回给用户,则注意用static final关键字修饰。
  • 除了构造方法,其他方法建议使用static静态方法

3、优点和适用场景

1)优点

  • 由于单例模式在内存中只有一个实例,减少了内存开支
  • 当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(注意Java的GC机制)。
  • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作

2)缺点(★★)

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  • 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。

3)应用场景(★★★★)

  • 要求生成唯一序列号的环境
  • 整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
  • 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式。

最佳实践:

  • Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理,等等。
  • 如果采用非单例模式(Prototype类型),则Bean初始化后的管理交由J2EE容器Spring容器不再跟踪管理Bean的生命周期

4、单例模式的扩展

1)懒汉式 - 线程不安全(★★★★)

public class Singleton {private static Singleton singleton = null;//限制产生多个对象private Singleton(){}//通过该方法获得实例对象public static Singleton getSingleton(){if(singleton == null){singleton = new Singleton();}return singleton;}
}

存在的问题:如一个线程A执行到singleton=new Singleton(),但还没有获得对象(对象初始化是需要时间的),第二个线程B
也在执行,执行到(singleton==null)判断,那么线程B获得判断条件也是为真,于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现两个对象

解决方法:使用Synchronized关键字修饰方法或者代码块,或者使用Reentrantlock,但是**仍然建议使用饿汉式单例模式,静态final对象无法被修改,可以保证线程安全 **!!

2)对象复制问题

Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法,则可以直接通过对象复制方式创建一个新对象。

由于单例类对象很少被要求复制,因此单例类不要实现Cloneable接口

3)多例模式(应该可以配合抽象工厂实现固定容量的线程池,快速响应提交的任务 ★★★★)

固定数量的皇帝类

public class Emperor {//定义最多能产生的实例数量private static int maxNumOfEmperor = 2;//每个皇帝都有名字,使用一个ArrayList来容纳,每个对象的私有属性private static ArrayList<String> nameList=new ArrayList<String>();//定义一个列表,容纳所有的皇帝实例private static ArrayList<Emperor> emperorList=new ArrayList<Emperor>();//当前皇帝序列号private static int countNumOfEmperor =0;//产生所有的对象static{for(int i=0;i<maxNumOfEmperor;i++){emperorList.add(new Emperor("皇"+(i+1)+"帝"));}}private Emperor(){//世俗和道德约束你,目的就是不产生第二个皇帝}//传入皇帝名称,建立一个皇帝对象private Emperor(String name){nameList.add(name);}//随机获得一个皇帝对象public static Emperor getInstance(){Random random = new Random();//随机拉出一个皇帝,只要是个精神领袖就成countNumOfEmperor = random.nextInt(maxNumOfEmperor);return emperorList.get(countNumOfEmperor);}//皇帝发话了public static void say(){System.out.println(nameList.get(countNumOfEmperor));}
}

这种需要产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题,提供系统的响应速度。例如读取文件,我们可以在系统启动时完成初始化工作,在内存中启动固定数量的reader实例,然后在需要读取文件时就可以快速响应

二、工厂模式

1、需求假设(★★★)

假设女娲可以通过八卦炉来造人,如果八卦炉的火候欠佳,则造出来白人;如果火候过旺,造出来黑人;如果火候刚刚好,则造出来黄种人,而不同人种除了肤色不同之外,语言也存在不同。

玉皇大帝知道女娲会造凡人,作为天神的他很好奇凡人究竟长啥样,因此跟女娲说:“女娲,凡人我还没见过,每个人种我都要一个,你能否帮我造出来?最近你不还要忙着补天吗,当然作为报酬,我宫里的玉石你顺便挑。”,女娲听了也随即答应了。这样女娲既要帮着生产(生产商),也要帮着采购(用户)。

2、工厂模式的定义和实现

1)定义(产品单一,★★★★)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

2)类图

3)可见性分析

先对上面的类图的可见性分析进行解释

  • 用户只关心能否给他3个不一样的人种,因此3个产品BlackHumanYellowHumanWhiteHuman对用户可见;
  • 用户并不关心这3个人种是怎么捏出来的,他只想看看不同人种长啥样、说什么方言而已。因此作为生产商的女娲,其八卦炉(AbstractHumanFactorycreateHuman())对用户可见,用于直接为用户生产3个人种。

4)代码实现(★★★★★)

a)人类接口
public interface Human {//每个人种的皮肤都有相应的颜色public void getColor();//人类会说话public void talk();
}
b)各人种实现类
public class BlackHuman implements Human {public void getColor(){System.out.println("黑色人种的皮肤颜色是黑色的!");}public void talk() {System.out.println("黑人会说话,一般人听不懂。");}
}---public class YellowHuman implements Human {public void getColor(){System.out.println("黄色人种的皮肤颜色是黄色的!");}public void talk() {System.out.println("黄色人种会说话,一般说的都是双字节。");}
}---public class WhiteHuman implements Human {public void getColor(){System.out.println("白色人种的皮肤颜色是白色的!");}public void talk() {System.out.println("白色人种会说话,一般都是但是单字节。");}
}
c)人类创造工厂类(★★★★)
//抽象人类创造工厂类
public abstract class AbstractHumanFactory {public abstract <T extends Human> T createHuman(Class<T> c);
}//人类创造工厂类
public class HumanFactory extends AbstractHumanFactory {public <T extends Human> T createHuman(Class<T> c){//定义一个生产的人种Human human=null;try {//产生一个人种human = (Human)Class.forName(c.getName()).newInstance();} catch (Exception e) {System.out.println("人种生成错误!");}return (T)human;}
}

Note

  • 在抽象工厂类中,采用了泛型(Generic)定义了createHuman(),通过定义泛型对createHuman的输入参数产生两层限制:

    • 必须是Class类型;
    • 必须Human的实现类
  • 这里的工厂实现类通过反射来创建对象
d)Main类
public class NvWa {public static void main(String[] args) {//声明阴阳八卦炉AbstractHumanFactory YinYangLu = new HumanFactory();//女娲第一次造人,火候不足,于是白人产生了System.out.println("--造出的第一批人是白色人种--");Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class);whiteHuman.getColor();whiteHuman.talk();//女娲第二次造人,火候过足,于是黑人产生了System.out.println("\n--造出的第二批人是黑色人种--");Human blackHuman = YinYangLu.createHuman(BlackHuman.class);blackHuman.getColor();blackHuman.talk();//第三次造人,火候刚刚好,于是黄色人种产生了System.out.println("\n--造出的第三批人是黄色人种--");Human yellowHuman = YinYangLu.createHuman(YellowHuman.class);yellowHuman.getColor();yellowHuman.talk();}
}

3、优点和适合的场景

1)优点(★★★★)

  • 良好的封装性:一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要**知道这个产品的类名(或约束字符串)**就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合
  • 扩展性好:只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成**“拥抱变化”。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类**,工厂类不用任何修改就可完成系统扩展。
  • 屏蔽产品类:产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口(如果存在多个产品,则关心多个产品的统一接口)。如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切
    换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
  • 工厂方法模式是典型的解耦框架
    • 高层模块只需要知道产品的抽象类,其他的实现类
      都不用关心,符合迪米特法则(最少知道原则)。
    • 符合依赖倒置原则(基于接口编程),只依赖产品类的抽象;
    • 符合里氏替换原则,使用产品子类替换产品父类

2)场景

工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度

4、工厂模式的扩展

1)简单(静态)工厂模式

如果只有一个工厂,则可以去掉AbstarctHumanFactory,直接使用工厂实现类,利用其public static <T extends Human> createHuman(Class<T> c)方法,创建并返回继承于父类Human的对象,类图如下:

简单工厂类的代码实现如下

public class HumanFactory {public static <T extends Human> T createHuman(Class<T> c){//定义一个生产出的人种Human human=null;try {//产生一个人种human = (Human)Class.forName(c.getName()).newInstance();} catch (Exception e) {System.out.println("人种生成错误!");}return (T) human;}
}

相应的Main类也要发生修改,这里就不附上代码了

简单(静态)工厂模式的缺点是扩展比较困难,不符合开闭原则。

2)多工厂模式(Executors ★★★★)

在使用工厂类初始化对象时,如果所有的产品类(黑/白/黄种人)都放到一个工厂方法中进行初始化会使代码结构不清晰。因此可以使用多工厂模式来分别创建。类图如下:

每个人种(具体的产品类)都对应了一个创建者,每个创建者都独立负责创建对应的产品对象,非常符合单一职责原则

工厂类代码如下:

//抽象工厂类
public abstract class AbstractHumanFactory {public abstract Human createHuman();
}
//工厂实现类1
public class BlackHumanFactory {public Human createHuman(){return new BlackHuman();}
}//工厂实现类2
public class WhiteHumanFactory {public Human createHuman(){return new WhiteHuman();}
}//工厂实现类3
public class YellowHumanFactory {public Human createHuman(){return new YellowHuman();}
}

Main类代码如下:

public class NvWa {public static void main(String[] args) {//女娲第一次造人,火候不足,于是白色人种产生了System.out.println("--造出的第一批人是白色人种--");Human whiteHuman = (new WhiteHumanFactory()).createHuman();whiteHuman.getColor();whiteHuman.talk();//女娲第二次造人,火候过足,于是黑色人种产生了System.out.println("\n--造出的第二批人是黑色人种--");Human blackHuman = (new BlackHumanFactory()).createHuman();blackHuman.getColor();blackHuman.talk();//第三次造人,火候刚刚好,于是黄色人种产生了System.out.println("\n--造出的第三批人是黄色人种--");Human yellowHuman = (new YellowHumanFactory()).createHuman();yellowHuman.getColor();yellowHuman.talk();}
}

优点:每一个产品类都对应了一个创建类,好处就是创建类的职责清晰,而且结构简单。

缺点

  • 增加扩展的难度:如果要扩展一个产品类,就需要建立一个相应的工厂类,这样增加了扩展的难度。
  • 增加维护的难度:因为工厂类和产品类的数量相同,维护时需要考虑两个对象之间的关系。

解决方法:在复杂应用中,对象如果和多个工厂类绑定则代码维护成本较高,这时可以增加一个协调类避免调用者与各个
子工厂交流,协调类的作用是封装子工厂类,对高层模块提供关于多个工厂类的统一访问接口

3)替代单例模式

单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象,类图如下所示(单例工厂聚合Singleton类(has a)):

Singleton定义了一个private无参构造函数,目的是不允许通过new的方式创建一个对象。

//单例类
public class Singleton {//不允许通过new产生一个对象private Singleton(){}public void doSomething(){//业务处理}
}

Singleton没有提供构造器,工厂方法如何创建单例对象呢? 答案是通过反射获取private构造器。

public class SingletonFactory {private static Singleton singleton;static{try {Class cl=Class.forName(Singleton.class.getName());//获得无参构造Constructorconstructor=cl.getDeclaredConstructor();//设置无参构造是可访问的constructor.setAccessible(true);//产生一个实例对象singleton = (Singleton)constructor.newInstance();} catch (Exception e) {//异常处理}}public static Singleton getSingleton(){return singleton;}
}

4)延迟初始化(★★★★)

工厂类可以用来实现延迟初始化,创建对象时先从缓存中(prMap)取,如果缓存中没有,再创建并插入prMap中,延迟初始化常用于线程池和数据库连接池的实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQ9lGN3J-1664638797387)(./img/Snipaste_2022-09-27_23-47-44.png)]

参考代码如下:

public class ProductFactory {private static final Map<String,Product> prMap = new HashMap();public static synchronized Product createProduct(String type) throws Exception{Product product =null;//如果Map中已经有这个对象if(prMap.containsKey(type)){product = prMap.get(type);}else{if(type.equals("Product1")){product = new ConcreteProduct1();}else{product = new ConcreteProduct2();}//同时把对象放到缓存容器中prMap.put(type,product);}return product;}
}

扩展场景:延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量Map<String,List<Product>> prMap),可以通过判断Map中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量

三、抽象工厂模式

1、需求假设

(工厂模式中假设的需求的后续)玉皇大帝收到3个人种的产品之后,欣赏了一番,但突然有了个疑问,他问女娲:“凡人是没有性别的吗?“, 这时女娲意识到她造的人没有性别,玉皇大帝看了女娲眉头紧锁,又有了新想法,让女娲造出男人和女人,让凡人在自己的土地上繁衍后代。因此女娲在多人种的基础上,造出了男人和女人,让男人和女人之间自由配对,完成社会文明的延续。

2、抽象工厂模式的定义和实现

1)定义(产品间具有依赖关系,★★★★)

为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类(并不是用了抽象工厂类就是抽象工厂模式)。

抽象工厂模式工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。

2)类图(男女人类 -> 黄种人抽象类 -> 接口)

Java典型类图,即一个接口,多个抽象类,然后是N个实现
,每个人种都是一个抽象类,性别是在各个实现类中实现的。

3)可见性分析

  • 用户关心的是男人和女人这两个产品,而不关心男人和女人的创建过程,因此用户只关心HumanFemaleFactoryMaleFactory这几个接口;

4)代码实现

a)人种接口
public interface Human {//每个人种都有相应的颜色public void getColor();//人类会说话public void talk();//每个人都有性别public void getSex();
}
b)抽象人种类
//白色人种
public abstract class AbstractWhiteHuman implements Human {//白色人种的颜色是白色的public void getColor(){System.out.println("白色人种的皮肤颜色是白色的!");}//白色人种讲话public void talk() {System.out.println("白色人种会说话,一般说的都是单字节。");}
}
//黑色人种
public abstract class AbstractBlackHuman implements Human {public void getColor(){System.out.println("黑色人种的皮肤颜色是黑色的!");}public void talk() {System.out.println("黑人会说话,一般人听不懂。");}
}
//黄色人种
public abstract class AbstractYellowHuman implements Human {public void getColor(){System.out.println("黄色人种的皮肤颜色是黄色的!");}public void talk() {System.out.println("黄色人种会说话,一般说的都是双字节。");}
}
c)人种实现类

以黄色人种为例:包括黄色男性人种和黄色女性人种

//黄人女性
public class FemaleYellowHuman extends AbstractYellowHuman {public void getSex() {System.out.println("黄人女性");}
}//黄人男性
public class MaleYellowHuman extends AbstractYellowHuman {public void getSex() {System.out.println("黄人男性");}
}
d)抽象工厂类(关于每个产品族的抽象)
public interface HumanFactory {//制造一个黄色人种public Human createYellowHuman();//制造一个白色人种public Human createWhiteHuman();//制造一个黑色人种public Human createBlackHuman();
}
e)工厂实现类(N个产品对应N个工厂实现类)

男性工厂实现类

public class MaleFactory implements HumanFactory {//生产出黑人男性public Human createBlackHuman() {return new MaleBlackHuman();}//生产出白人男性public Human createWhiteHuman() {return new MaleWhiteHuman();}//生产出黄人男性public Human createYellowHuman() {return new MaleYellowHuman();}
}

女性工厂实现类

public class FemaleFactory implements HumanFactory {//生产出黑人女性public Human createBlackHuman() {return new FemaleBlackHuman();}//生产出白人女性public Human createWhiteHuman() {return new FemaleWhiteHuman();}//生产出黄人女性public Human createYellowHuman() {return new FemaleYellowHuman();}
}
f)Main类
public class NvWa {public static void main(String[] args) {//第一条生产线,男性生产线HumanFactory maleHumanFactory = new MaleFactory();//第二条生产线,女性生产线HumanFactory femaleHumanFactory = new FemaleFactory();//生产线建立完毕,开始生产人了:Human maleYellowHuman = maleHumanFactory.createYellowHuman();Human femaleYellowHuman = femaleHumanFactory.createYellowHuman();System.out.println("---生产一个黄色女性---");femaleYellowHuman.getColor();femaleYellowHuman.talk();femaleYellowHuman.getSex();System.out.println("\n---生产一个黄色男性---");maleYellowHuman.getColor();maleYellowHuman.talk();maleYellowHuman.getSex();/** .....* 后面继续创建不同人种的男性,女性*/}
}

3、优点和适合场景

1)小总结(★★★★★)

这里以女娲造男女人来解释抽象工厂方法(相比于工厂模式,抽象工厂模式有多条生产线,这里使为了让男女之间产生爱情,因此产品是男女,男女之间具有相关性,两个产品由最终的两个工厂实现类创建)

  • N个产品(男/女)则有N产品实体类;
  • 每个产品中有M个族(黑/白/黄人,产品族为3)则有M抽象产品类(不同族有不同语言);
  • N个产品(男/女)则有N工厂实现类;(不同工厂实现类对应不同的产品生产线
  • M个产品族(黑/白/黄人)则有M抽象工厂方法(1个抽象工厂类);
  • N工厂实现类中,每个工厂类可以生产1个产品族(黑男/黑女)

更加简化的抽象工厂类图如下所示:

  • 抽象产品类和抽象工厂类绑定
  • 产品实现类和工厂实现类绑定

2)优缺点(★★★)

a)优点
  • 封装性:每个产品的实现类不是高层模块要关心的,它要关心的是什么?是接口,是抽象
  • 产品族内的约束非公开状态:如果女娲在造人的时候在产品族内增加了约束,比如为了**“满足白人出生率低,黄种人出生率高“的约束**,假设生产出来的男人在黑人和黄种人的占比为1:1,则在生产出的女人在白人和黄种人的占比可以配置为1:10。这个生产过程对调用工厂类的高层模块是透明的(只是举个例子,不提倡一夫多妻)
b)缺点

抽象工厂模式的最大缺点是产品族扩展非常困难,但对于产品扩展是非常方便的

  • 如果在产品中加入一个新的产品中性人 intersex),只需要增加一个关于中性人的工厂实体类即可;
  • 如果增加一个新的产品族(蓝人 BlueHuman),则需要在抽象工厂类中增加一个新的工厂方法(createBlueHuman() ),相应在每个工厂实体类(黑/白/黄人)中去实现抽象工厂类增加createBlueHuman() ;这一点违背了开闭原则(修改无法关闭)

因此抽象工厂模式适合于横向扩展(增加产品),不适合于纵向扩展(增加产品族)

3)应用场景(★★★★)

抽象工厂模式的使用场景定义非常简单:一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。

比如一个文本编辑器和一个图片处理器都是软件实体,但是Unix下的文本编辑器和Windows下的文本编辑器虽然功能和界面都相
,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条件:操作系统类型(造人的共同约束条件是人的性别)。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片处理器,参考类图如下:

Note:不同操作系统的产品分为两个产品,每个系统产品中包含文本编辑器,图片处理器,即为两个产品族

四、生成器模式

1、需求假设

A公司与汽车模型制造商B签了一个合同,将奔驰和宝马的车辆模型交给B去做,并额外增加了一个新的需求:汽车的启动、停止、喇叭声都由客户A自己控制。其中奔驰模型A是先有引擎声音,然后再响喇叭;奔驰模型B是先启动起来,然后再有引擎声音。

2、生成器模式的定义和实现

1)定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

2)类图(★★★★)

Note

  • 生产出的N多个奔驰和宝马车辆模型都有run()方法,但是具体到每一个模型的run()方法中间的执行任务的顺序是不同的,A说要啥顺序,B给啥顺序。
  • CarModel中定义的setSequence方法,客户A可以通过该方法配置要执行的顺序,run()则会根据sequence定义的顺序完成指定的顺序动作。

3)可见性分析(★★★)

  • 用户虽然关心车辆模型是否支持对基本动作(startstopalarm)顺序的手动设置(setSequence),以及车辆整体的表现结果(run),但实际关心的是最终得到的不同类型的产品(getABenzModel(),所以对于setSequencerun可以由建造者来完成,而通过导演类为用户提供一个统一的接口getABenzModel()
  • 一个建造者对应一个用户的需求,对应一个车辆模型的组装顺序,为了给用户提供一个统一的接口,这里将所有建造者统一用导演类来管理
  • 对于抽象类中的run(),为了防止实现类中对其进行重写,需要对其使用final修饰。
  • 对于抽象类的基本方法,用户不可见但实现类可见,因此用protected修饰。

4)代码实现

a)车辆模型抽象类
public abstract class CarModel {//这个参数是各个基本方法执行的顺序private ArrayList<String> sequence = new ArrayList<String>();//模型是启动开始跑了protected abstract void start();//能发动,那还要能停下来,那才是真本事protected abstract void stop();//喇叭会出声音,是滴滴叫,还是哔哔叫protected abstract void alarm();//引擎会轰隆隆地响,不响那是假的protected abstract void engineBoom();//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑final public void run() {//循环一边,谁在前,就先执行谁for(int i=0;i<this.sequence.size();i++){String actionName = this.sequence.get(i);if(actionName.equalsIgnoreCase("start")){this.start(); //开启汽车}else if(actionName.equalsIgnoreCase("stop")){this.stop(); //停止汽车}else if(actionName.equalsIgnoreCase("alarm")){this.alarm(); //喇叭开始叫了}else if(actionName.equalsIgnoreCase("engine boom")){ //如果是engine boom关键字this.engineBoom(); //引擎开始轰鸣}}}//把传递过来的值传递到类内final public void setSequence(ArrayList<String> sequence){this.sequence = sequence;}
}
b)奔驰和宝马模型
//Benz
public class BenzModel extends CarModel {protected void alarm() {System.out.println("奔驰车的喇叭声音是这个样子的...");}protected void engineBoom() {System.out.println("奔驰车的引擎室这个声音的...");}protected void start() {System.out.println("奔驰车跑起来是这个样子的...");}protected void stop() {System.out.println("奔驰车应该这样停车...");}
}//BMW
public class BMWModel extends CarModel {protected void alarm() {System.out.println("宝马车的喇叭声音是这个样子的...");}protected void engineBoom() {System.out.println("宝马车的引擎室这个声音的...");}protected void start() {System.out.println("宝马车跑起来是这个样子的...");}protected void stop() {System.out.println("宝马车应该这样停车...");}
}
c)Main类生产BMW模型
public class Main {public static void main(String[] args) {/** 客户告诉XX公司,我要这样一个模型,然后XX公司就告诉我老大* 说要这样一个模型,这样一个顺序,然后我就来制造*/BenzModel benz = new BenzModel();//存放run的顺序ArrayList<String> sequence = new ArrayList<String>();sequence.add("engine boom"); //客户要求,run的时候时候先发动引擎sequence.add("start"); //启动起来sequence.add("stop"); //开了一段就停下来//我们把这个顺序赋予奔驰车benz.setSequence(sequence);benz.run();}
}
d)抽象汽车组装类(使用建造者管理Main类,为用户提供统一的接口)
public abstract class CarBuilder {//建造一个模型,你要给我一个顺序要,就是组装顺序public abstract void setSequence(ArrayList<String> sequence);//设置完毕顺序后,就可以直接拿到这个车辆模型public abstract CarModel getCarModel();
}
e)车模型组装者
//Benz
public class BenzBuilder extends CarBuilder {private BenzModel benz = new BenzModel();public CarModel getCarModel() {return this.benz;}public void setSequence(ArrayList<String> sequence) {this.benz.setSequence(sequence);}
}//BMW
public class BMWBuilder extends CarBuilder {private BMWModel bmw = new BMWModel();public CarModel getCarModel() {return this.bmw;}public void setSequence(ArrayList<String> sequence) {this.bmw.setSequence(sequence);}
}
f)导演类
public class Director {private ArrayList<String> sequence = new ArrayList();private BenzBuilder benzBuilder = new BenzBuilder();private BMWBuilder bmwBuilder = new BMWBuilder();/** A类型的奔驰车模型,先start,然后stop,其他什么引擎了,喇叭一概没有*/public BenzModel getABenzModel(){//清理场景,这里是一些初级程序员不注意的地方this.sequence.clear();//这只ABenzModel的执行顺序this.sequence.add("start");this.sequence.add("stop");//按照顺序返回一个奔驰车this.benzBuilder.setSequence(this.sequence);return (BenzModel)this.benzBuilder.getCarModel();}/** B型号的奔驰车模型,是先发动引擎,然后启动,然后停止,没有喇叭*/public BenzModel getBBenzModel(){this.sequence.clear();this.sequence.add("engine boom");this.sequence.add("start");this.sequence.add("stop");this.benzBuilder.setSequence(this.sequence);return (BenzModel)this.benzBuilder.getCarModel();}...
}

Note:由于一个建造者对应一个需求,如果每个建造者都和客户绑定耦合度太高,所以可以使用导演类为用户提供一个统一的接口

3、优点和适用场景

1)优点

  • 封装性:使用建造者模式可以使客户端不必知道产品内部组成的细节,如例子中我们就不需要关心每一个具体的模型内部是如何实现的,产生的对象类型就是CarModel
  • 不同建造者独立,容易扩展

2)应用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使
    用该模式。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模
    式非常合适。
  • 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易
    得到时,也可以采用建造者模式封装该对象的创建过程。

3)建造者和工厂方法的区别(★★★★★)

  • 建造者模式最主要的功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗地说就是零件的装配,顺序不同产生的对象也不同
  • 工厂方法则重点是创建,创建零件是它的主要职责,组装顺序则不是它关心的

五、原型模式

Note:原型模式和单例模式很好理解,这里简单介绍

1、需求假设

实现个性化电子账单,为每个客户发送个性化的邮件,每个邮件中包含每个客户的个人信息。

2、定义和实现

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

类图

如果AdvTemplate是从数据库中读取到的电子账单模板,Mail是一封邮件类,发送邮件时对该对象进行操作。

如果采用单线程发送,按照一封邮件发出去需要0.02秒(够小了,你还要到数据库中取数据呢),600万封邮件需要33个小时。

如果采用多线程发送,在产生第一封邮件对象,放到线程1中运行,还没有发送出去;线程2也启动了,直接就把邮件对象mail的收件人地址和称谓修改掉了,线程不安全了。

因此有两种解决方法:

  • new一个新的mail对象
  • 对象的复制(clone):如果有对象之间存在很多公共的信息clone效率比new高。

因此类图修正如下:

参考java重写clone()方法

1)浅拷贝

java浅克隆,即在实现Clonable接口时,重写Objectclone方法,并转化成子类类型对象(Person p = (Person) super.clone()),但如果子类对象中有引用类型(Address),clone的对象p与原型对象所指向的Address对象是相同的

public class Person{private Address address;@Overrideprotected Person clone() {// throws CloneNotSupportedException  写到下面//return (Person)super.clone();Person p=null;try{p=(Person)super.clone();}catch(CloneNotSupportedException e){throw new RuntimeException(e);//e.printStackTrace();}return p;}
}

2)深拷贝(★★★★)

java深克隆则要求所有Person对象的所有引用类型都要重写clone方法,比如 p=(Person)super.clone();p.address = address.clone();

public class Address{@Overrideprotected Address clone() {// throws CloneNotSupportedException  写到下面//return (Person)super.clone();Addressaddr=null;try{addr=(Address)super.clone();}catch(CloneNotSupportedException e){throw new RuntimeException(e);//e.printStackTrace();}return addr;}
}public class Person{private Address address;@Overrideprotected Person clone() {// throws CloneNotSupportedException  写到下面//return (Person)super.clone();Person p=null;try{p=(Person)super.clone();p.address = address.clone();}catch(CloneNotSupportedException e){throw new RuntimeException(e);//e.printStackTrace();}return p;}
}

3、应用场景(★★★★)

原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示这个对象是可拷贝的,Cloneable接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。

1)优点:

  • 性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
  • 在调用clone()方法时,JVM会根据这个标识在堆内存中以二进制的方式拷贝对象,重新分配一个内存块,并没有执行构造函数new

2)应用场景

  • 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
  • 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;
  • 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时可以考虑使用
    原型模式拷贝多个对象供调用者使用(JMM模型,volatile关键字)。
  • 一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

3)注意事项

  • 构造函数不会被执行
  • 深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现(这个场景不是很明白??)
  • 对象的clone与对象内的final关键字是有冲
    突的,原因是final类型是无法赋值的,所以无法通过clone之后对其进行修改。因此要使用clone方
    法,在类的成员变量上就不要增加final关键字

创建型设计模式(待更新)相关推荐

  1. Java设计模式(二)创建型设计模式

    文章目录 三 创建型设计模式 3.1 单例设计模式 3.1.1 饿汉式(线程安全) 3.1.2 懒汉式(线程不安全) 3.1.3 优缺点 3.1.4 补充 3.1.5 框架中的使用 3.1.4.1 S ...

  2. 02.创建型设计模式实验

    写在前面 在网上抄个作业居然要不没有,要不要钱!老子有个屁的钱,气得我直接写完了,分享在这里供借鉴(抄可以,但你得会了再抄).随便转载,可以不署我名但需要声明非原创. 这是一系列的作业,所以也气了我一 ...

  3. 技术图文:02 创建型设计模式(下)

    创建型设计模式(下) 知识结构: 图1 知识结构 单例模式 – 确保对象的唯一性 Sunny 软件公司承接了一个服务器负载均衡软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量 ...

  4. 技术图文:02 创建型设计模式(上)

    创建型设计模式(上) 知识结构: 图1 知识结构 简单工厂模式 Sunny 软件公司欲基于 C# 语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,如: 柱状图(histogram) ...

  5. 创建型设计模式对比总结 设计模式(八)

    创建型模式是new 的一种替代方式,可以将对象的创建与具体的类型进行分离 目前已经介绍了5种创建型设计模式(如果简单工厂算一种的话,那就是6种) 分别是: 简单工厂模式.工厂方法模式.抽象工厂模式.建 ...

  6. java面向对象程序设计第三版_JAVA面向对象程序设计之创建型设计模式

    [本文详细介绍了JAVA面向对象程序设计中的创建型设计模式,欢迎读者朋友们阅读.转发和收藏!] 1 基本概念 1.1 什么是设计模式 设计模式( Design pattern )是一套被反复使用.多数 ...

  7. 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 第1章 Python的创建型设计模式...

    本节书摘来自华章出版社<Python编程实战:运用设计模式.并发和程序库创建高质量程序>一 书中的第1章,第1.1节,作者:(美) Mark Summerfield,更多章节内容可以访问云 ...

  8. 从框架源码中学习创建型设计模式

    文章目录 从框架源码中解读创建型设计模式 工厂模式 案例一:RocketMQ源码-创建Producer生产者 案例二:RocketMQ源码-创建过滤器工厂 抽象工厂 案例一:Dubbo源码-创建缓存的 ...

  9. java设计模式之创建型设计模式

    创建型设计模式: 抽象工厂(Abstract Factory): 用途:提供一个接口以创建一系列相关或相互依赖的对象,而无需指定具体的类. 场景: 一个系统要独立于它的产品的创建. 一个系统要由多个产 ...

最新文章

  1. JavaScript的语言标准
  2. 获取MySQL数据库表结构的信息(字段名,字段类型,注释等)
  3. 阿里文娱测试开发专家谈《算法基石:实时数据质量如何保障?》
  4. 手环是如何测试人体健康数据?
  5. 表面配准论文1--基于高阶图匹配方法的稠密表面配准
  6. LeetCode 1028. 从先序遍历还原二叉树(栈)
  7. h5 nan_手把手教你将H5游戏打包成快游戏
  8. 2018最火机器学习项目盘点—CV项目领冠榜单
  9. 计算机视觉空间域(spatial)注意力机制——CBAM
  10. vsftp不同帐号的目录和权限
  11. Python实现图像信息隐藏
  12. Atitit 运维之道 v2 s09.docx Atitit 运维之道 目录 1. 概念 2 1.1. devops算是最低门槛了。什么运维平台,搞来搞去也就那些东西。无外乎cmdb、部署、监
  13. 常用测试用例设计方法
  14. http请求过程:一,DNS域名解析系统详解
  15. 对数回归 matlab,高斯过程回归GPR-MATLAB语法解释
  16. Ps快捷键及基础知识
  17. 企业该如何做好IT规划
  18. ubuntu ibus拼音异常
  19. 计算机考研复试-计算机组成原理
  20. Discom BKS03/KS91D传感器

热门文章

  1. opencv+nvcodec实现视频硬解码
  2. 汽车CAN通信解析(二)
  3. 原子战舰与8266代码理解(while循环后直接加分号)
  4. ubuntu安装有道词典
  5. python求15 17 23 65 97的因数_笨方法学python,Lesson15,16,17
  6. 编译原理实验(三)词法语法分析综合设计
  7. pdf.js 修改预览页面title
  8. 如何将pdf修改编辑
  9. Bilateral Filtering(双边滤波)
  10. 管理博文 畅购商城--oauth---09