设计模式——创建型模式(工厂,简单工厂,单例,建造者,原型)

  • 2019 年 10 月 16 日
  • 笔记

创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离

为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则

创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建、如何组合在一起来实现使整个系统独立的目的

一、工厂模式

简单工厂模式

1、楔子

某暴发户有若干辆车,每辆车品牌都不同(如奔驰、宝马、奥迪),这些车来自同一个父类,在继承父类后不同的子类修改了部分属性从而使得它们产自不同的品牌

如果司机希望暴发户坐车时,不需要知道这些具体车辆类的名字,只需要知道表示该车辆类的一个参数;同时提供一个方便调用的方法,只要暴发户把参数传入方法就能得到一个相应的车辆对象,这时就能使用简单工厂模式

2、解析

又称静态工厂方法模式

在简单工厂模式中,可以根据不同参数返回不同的类的实例

该模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类

简单工厂模式存在的目的是定义一个用于创建对象的接口

模式组成有:

  1. 工厂类角色:简单工厂模式的核心,含有一定的商业逻辑和判断逻辑。在 Java中由具体类实现
  2. 抽象产品角色:一般是具体产品继承的父类或实现的接口。在 Java中由接口或抽象类实现
  3. 具体产品角色:工厂类创建的对象就是本类的实例

3、举例

若这个暴发户有三辆车,Benz、Bmw、Audi。每次坐车时说话都很奇怪:坐 Benz时说“开奔驰车!”,坐 Bmw时说“开宝马车!”,坐 Audi时说“开奥迪车!”。你肯定会觉得这人有病,直接说开车不就行了?

现在用简单工厂模式改造暴发户的坐车方式——暴发户只需跟司机说“奔驰 / 宝马 / 奥迪”就行了

//抽象产品角色  interface Car {      public void drive();  }    //具体产品角色  class Benz implements Car {      public void drive() {          System.out.println("开奔驰!");      }  }  class Bmw implements Car {      public void drive() {          System.out.println("开宝马!");      }  }  class Audi implements Car {      public void drive() {          System.out.println("开奥迪!");      }  }    //工厂类角色  class Driver {      //工厂方法,注意返回类型为抽象产品角色      public static Car driverCar(String s) throws Exception {          //判断逻辑,返回具体的产品角色给 Client          if(s.equalsIgnoreCase("Benz"))              return new Benz();          else if(s.equalsIgnoreCase("Bmw"))              return new Bmw();          else if(s.equalsIgnoreCase("Audi"))              return new Audi();          else throw new Exception();          ...      }  }    //欢迎暴发户登场  public class Magnate {      public static void main(String[] args) {          try {              //告诉司机今天坐奔驰              Car car = Driver.driverCar("Benz");              //下令开车              car.drive();              ...          }      }  }

工厂方法模式

1、楔子

从开闭原则分析简单工厂模式,暴发户增加一辆车时,只要符合抽象产品指定合同,再通知工厂类即可使用,对产品部分而言符合开闭原则

但是对工厂类貌似不太理想,每增加一辆车,工厂类就要增加相应的业务逻辑或判断逻辑,显然违背了开闭原则。新产品的加入使工厂类非常被动,这种工厂类称为全能类或上帝类

实际应用中,产品可能是一个多层次的树状结构,简单工厂模式中只有一个工厂类,难以应付多种情况,于是出现了工厂方法模式

2、解析

工厂方法模式去掉了简单工厂模式中工厂类的静态属性,使得它可以被子类继承。这样简单工厂模式中集中在工厂方法上的压力可由工厂方法模式中的不同类分担

模式组成:

  1. 抽象工厂角色:工厂方法模式的核心,是具体工厂角色必须实现的接口或必须继承的父类。与应用程序无关。在 Java中由抽象类或者接口实现
  2. 具体工厂角色:含和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象
  3. 抽象产品角色:具体产品继承的父类或实现的接口。在 Java中由抽象类或接口实现
  4. 具体产品角色:具体工厂角色创建的对象就是此角色的实例

工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的上帝类,以此分担对象承受的压力

适用工厂方法模式的情景:

  1. 客户程序不需要知道要使用对象的创建过程
  2. 客户程序使用的对象存在变动的可能,或者根本不知道使用哪个具体的对象

3、举例

话说暴发户生意越做越大,爱车越来越多。司机感觉很卑微,所有车他都要记住、维护,都必须经过他来使用。某天暴发户良心发现,告诉司机“今后你不用这么辛苦了,我给你安排了几个人手,你只用管理他们就行了”

//抽象产品角色、具体产品角色与简单工厂模式类似,这里略    //抽象工厂角色  interface Driver {      public Car driverCar();  }  class BenzDriver implements Driver {      public Car driverCar() {          return new Benz();      }  }  class BmwDriver implements Driver {      public Car driverCar() {          return new Bmw();      }  }  class AudiDriver implements Driver {      public Car driverCar() {          return new Audi();      }  }    //和具体产品形成对应关系    //暴发户登场  public class Magnate {      public static void main(String[] args) {          try {              Driver driver = new BenzDriver();              Car car = driver.driverCar();              car.drive();          }          ...      }  }

工厂方法模式虽然解决了简单工厂模式的一些不足,但使对象的数量成倍增长,当产品种类很多时会出现大量对应的工厂对象,所以使用者可根据自身情况结合使用两种工厂模式

二、抽象工厂模式

产品族:位于不同产品等级结构中,但功能相关联的产品组成的家族

K9khZj.png

图中 BmwCar和 BenzCar是两个产品树(产品层次结构)。而 BenzSportsCar和 BmwSportsCar就是一个产品族,它们都属于跑车家族

1、解析

抽象工厂模式和工厂方法模式的区别在于创建对象的复杂度上,前者是三种工厂模式中最抽象、最具一般性的

抽象工厂模式目的:为客户端提供一个接口,可以创建多个产品族中的产品对象

使用抽象工厂模式要满足以下条件:

  1. 系统中有多个产品族,但系统一次只能消费其中一族产品
  2. 属于同一产品族的产品一起使用

模式组成:(和工厂方法模式如出一辙)

  1. 抽象工厂角色:工厂方法模式的核心,是具体工厂角色必须实现的接口或必须继承的父类。与应用程序无关。在 Java中由抽象类或者接口实现
  2. 具体工厂角色:含和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象
  3. 抽象产品角色:具体产品继承的父类或实现的接口。在 Java中由抽象类或接口实现
  4. 具体产品角色:具体工厂角色创建的对象就是此角色的实例

2、举例

//抽象产品角色  abstract class Video {      public abstract void produce();  }    //具体产品角色  class PythonVideo extends Video {      public void produce() {          System.out.println("Python课程视频");      }  }  class JavaVideo extends Video {      public void produce() {          System.out.println("录制Java课程视频");      }  }    //抽象工厂角色  interface CourseFactory {      Video getVideo();      Article getArticle();  }    //具体工厂角色  class JavaCourseFactory implements CourseFactory {      public Video getVideo() {          return new JavaVideo();      }      public Article getArticle() {          return new JavaArticle();      }  }  class PythonCourseFactory implements CourseFactory {      public Video getVideo() {          return new PythonVideo();      }      public Article getArticle() {          return new PythonArticle();      }  }

三、单例模式

单例模式在各种开源框架、应用系统中多有应用

1、解析

又称单态模式或单件模式

单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。其目的是控制特定的类只产生一个对象,当然特殊情况下允许灵活改变对象的个数

实现单例模式的方法:将构造函数私有化(至少是受保护的),使得外面的类不能通过引用产生对象。用户通过调用类方法得到类的对象

单例模式分为有状态和无状态。有状态的单例对象一般是可变单例对象,多个对象在一起可以作为一个状态仓库向外提供服务;没有状态的单例对象一般是不可变单例对象,仅用作提供工具函数

2、举例

//饿汉式  public class Singleton {      //在自己内部定义自己的一个实例。且是 private类型,只供内部使用      private static Singleton instance = new Singleton();        //再将构造函数私有化      private Singleton() {}        //静态工厂方法,提供一个供外部访问得到对象的方法      public static Singleton getIntance() {          return instance;      }  }
//懒汉式  public class Singleton {      //注意不同点      private static Singleton instance = null;        //再将构造函数私有化      private Singleton() {}        //静态工厂方法,提供一个供外部访问得到对象的方法      public static synchronized Singleton getIntance() {          if(null == instance) {              instance = new Singleton();          }          return instance;      }  }

比较两种实现方式:

  • 两者的构造函数都是私有的,断开了通过构造函数获得实例的渠道,同时失去了类的多态性
  • 后者对静态工厂方法进行了同步处理,防止多线程环境产生多个实例,而前者不存在这种情况
  • 前者在类的加载时实例化,导致多次加载造成多次实例化,后者将类对自己的实例化延迟到第一次被引用,但因为同步处理的原因降低了反应速度

上述两种方式均失去了多态性,不允许被继承。灵活点的实现是,将构造函数设置为受保护的,这样允许被继承产生子类。新方法在具体实现上有所不同,可以将父类中获得对象的静态方法放到子类中再实现,也可以在父类的静态方法中进行条件判断来决定获得哪个对象

// GOF认为最好的方式是维护一张存有对象和对应名称的注册表(可用 HashMap实现  import java.util.HashMap;    class Singleton { //父类      //存放对应关系      private static HashMap registry = new HashMap();      static private Singleton s = new Singleton();        //受保护的构造函数      protected Singleton() {}      public static Singleton getInstance(String name) {          if(name == null) {              name == "Singleton";          }          if(registry.get(name) == null) {              try {                  registry.put(name, Class.forName(name).newInstance());              } catch(Exception e) {                  e.printStackTrace();              }          }          return (Singleton)registry.get(name);      }      public void test() {          System.out.println("Success!");      }  }    class SingletonChild extends Singleton { //子类      public SingletonChild() {}      public static SingletonChild getInstance() {          return (SingletonChild)Singleton.getInstance("SingletonChild");      }      public void test() {          System.out.println("Success Again!");      }  }

由于 Java中子类构造函数的范围不能比父类的小,所以可能有不守规则的客户程序使用其构造函数产生实例,导致单例模式失效

四、建造者模式

1、楔子

在电脑装配工厂中,有很多工人在熟练的装机。他们不管用户使用的 CPU是 Intel还是 AMD,也不管显卡是上千的还是白送的,都能三下五除二迅速组装起来——一台 PC就这么诞生了。对于客户而言,并不清楚太多关于 PC组装的细节,这和建造者模式十分相似

2、解析

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

创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

建造者模式一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,而不需要知道内部的具体构建细节

模式组成:

  1. 抽象建造者角色:规范产品对象各个组成部分的建造。一般独立于应用程序的业务逻辑
  2. 具体建造者角色:与应用程序紧密相关的类,在指导者的调用下创建产品实例。该角色在实现抽象建造者角色提供方法的前提下完成产品组装
  3. 指导者角色:调用具体建造者角色创建产品对象
  4. 产品角色:建造的复杂对象。包括定义组件的类和将这些组件装配成产品的接口

适用场景:

  • 隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
  • 多个部件都可以装配到一个对象中,但产生的运行结果不相同
  • 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
  • 初始化一个对象时,参数过多,或者很多参数具有默认值
  • Builder模式不适合创建差异性很大的产品类。产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本
  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性

3、举例

下面我们使用建造者模式来构造共享单车

//产品类  class Bike {      private IFrame frame;      private ISeat seat;      private ITire tire;        public IFrame getFrame() {          return frame;      }      public void setFrame(IFrame frame) {          this.frame = frame;      }      public ISeat getSeat() {          return seat;      }      public void setSeat(ISeat seat) {          this.seat = seat;      }      public ITire getTire() {          return tire;      }      public void setTire(ITire tire) {          this.tire = tire;      }  }    //抽象建造者类  abstract class Builder {      abstract void buildFrame();      abstract void buildSeat();      abstract void buildTire();      abstract Bike createBike();  }  //具体建造者类  class MobikeBuilder extends Builder{ //摩拜单车      private Bike mBike = new Bike();        void buildFrame() {          mBike.setFrame(new AlloyFrame());      }      void buildSeat() {          mBike.setSeat(new DermisSeat());      }      void buildTire() {          mBike.setTire(new SolidTire());      }      Bike createBike() {          return mBike;      }  }  class OfoBuilder extends Builder{ //OFO小黄车      private Bike oBike = new Bike();        void buildFrame() {          oBike.setFrame(new CarbonFrame());      }      void buildSeat() {          oBike.setSeat(new RubberSeat());      }      void buildTire() {          oBike.setTire(new InflateTire());      }      Bike createBike() {          return oBike;      }  }    //指挥者类  class Director {      private Builder mBuilder = null;        public Director(Builder builder) {          mBuilder = builder;      }      public Bike construct() {          mBuilder.buildFrame();          mBuilder.buildSeat();          mBuilder.buildTire();          return mBuilder.createBike();      }  }    //客户端使用  public class Click {      public static void main(String[] args) {          showBike(new OfoBuilder());          showBike(new MobikeBuilder());      }      private void showBike(Builder builder) {          Director director = new Director(builder);          Bike bike = director.construct();          bike.getFrame().frame();          bike.getSeat().seat();          bike.getTire().tire();      }  } 

五、原型模式

1、楔子

古人云:书非借不能读也。本人深谙古人教诲,希望博览群书。奈何没钱只能办一张借书卡。但是借书的一个缺点,如果我看到有用的地方想进行标记时却不能动笔,无奈之下,我只能将这页内容复印下来,这样就能保存自己的圈圈划划。而原型模型和这个类似

2、解析

原型模式用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象

基本工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝原型自己来实现创建过程

模式组成:

  1. 客户角色:让一个原型克隆自己以得到一个新对象
  2. 抽象原型角色:实现自己的 clone方法。通常是抽象类,具有许多具体的子类
  3. 具体原型角色:被复制的对象。是抽象原型角色的具体子类

在原型模式结构中定义了一个抽象原型类,所有的 Java类都继承自java.lang.Object,而 Object类提供一个clone()方法,可以将一个 Java对象复制一份。因此在 Java中可以直接使用 Object提供的 clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单

能够实现克隆的 Java类必须实现一个标识接口 Cloneable,表示这个 Java类支持复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常

通常情况下,一个类包含一些成员对象,在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆

  • 在浅克隆中,如果原型对象的成员变量是值类型(八大基本类型,byte,short,int,long,char,double,float,boolean)就直接复制,如果是复杂的类型(枚举,String,对象)就只复制对应的内存地址
  • 深克隆则是全部复制,然后各自独立。修改克隆对象对原型对象没有丝毫影响

适用场景:

  1. 对象种类繁多,无法将他们整合到一个类
  2. 难以根据类生成实例时
  3. 想解耦框架与生成的实例

3、举例

//附件类  class Attachment {      private String name; //附件名        public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }        public void download() {          System.out.println("下载附件"+name);      }  }    //周报类:里面很多属性可以忽略,但再真正的操作时是确实存在的  //关键点在于,实现 cloneable接口以及用 object的 clone方法  class WeeklyLog implements Cloneable{      private Attachment attachment;      private String date;      private String name;      private String content;        public Attachment getAttachment() {          return attachment;      }      public void setAttachment(Attachment attachment) {          this.attachment = attachment;      }      public String getDate() {          return date;      }      public void setDate(String date) {          this.date = date;      }      public String getName() {          return name;      }      public void setName(String name) {          this.name = name;      }      public String getContent() {          return content;      }      public void setContent(String content) {          this.content = content;      }        //通过 clone()方法实现浅克隆      public WeeklyLog clone() {          //需要实现 cloneable的接口,直接继承 object就好,它里面自带一个clone方法          Object obj = null;          try {              obj = super.clone();              return (WeeklyLog)obj;          } catch (CloneNotSupportedException e) {              // TODO Auto-generated catch block              System.out.println("不支持克隆方法!");              return null;          }      }  }    //测试类,客户端  public class Client {      public static void main(String[] args) {          WeeklyLog log_1,log_2;          log_1 = new WeeklyLog();    //创建原型对象          Attachment attachment = new Attachment(); //创建附件对象          log_1.setAttachment(attachment);    //将附件添加到周报种去          log_2=log_1.clone();    //克隆周报          System.out.println("周报是否相同"+(log_1==log_2));          System.out.println("附件是否相同"+(log_1.getAttachment()==log_2.getAttachment()));      }  }