欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

创建型设计模式实战-工厂、单例、建造者
(last modified Sep 8, 2022, 1:05 AM )
by
侧边栏壁纸
  • 累计撰写 190 篇文章
  • 累计创建 65 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

创建型设计模式实战-工厂、单例、建造者

橙序员
2022-08-14 / 0 评论 / 0 点赞 / 591 阅读 / 7,781 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

创建型模式概述创建型设计模式包括工厂模式、抽象工厂模式、单例模式、建造者模式和原型模式工厂模式定义父类定义了创建对象的接口,让子类决定实例化哪个类。角色角色图:产品:抽象类,决定了实例所持有的接口创建者:负责生产产品的抽象类具体的产品:决定了具体的实例具体的创建者:负责生产具体的产品使用spring

创建型模式概述

创建型设计模式就是创建对象的模式,它抽象了实例化的过程。包括工厂模式、抽象工厂模式、单例模式、建造者模式和原型模式

SPRING-BEAN的作用域

要使用spring来实现创建型模式,我们要首先知道bean的作用域有哪些:

singleton

singleton是scope属性的默认值,当我们把bean的scope属性设置为singleton时,代表将对该bean使用单例模式.单例想必大家都熟悉,也就是说每次使用该bean的id从容器中获取该bean的时候,都将会返回同一个bean实例。但这里的单例跟设计模式里的单例还有一些小区别。

设计模式中的单例是通过硬编码,给某个类仅创建一个静态对象,并且只暴露一个接口来获取这个对象实例,因此,设计模式中的单例是相对ClassLoader而言的,同一个类加载器下只会有一个实例。
但是在Spring中,singleton单例指的是每次从同一个IOC容器中返回同一个bean对象,单例的有效范围是IOC容器,而不是ClassLoader。IOC容器会将这个bean实例缓存起来,以供后续使用。

prototype

接下来是另一个常用的scope:prototype。与singleton相反,设置为prototype的bean,每次调用容器的getBean方法或注入到另一个bean中时,都会返回一个新的实例
与其他的scope类型不同的是,Spring并不会管理设置为prototype的bean的整个生命周期,获取相关bean时,容器会实例化,或者装配相关的prototype-bean实例,然后返回给客户端,但不会保存prototype-bean的实例。所以,尽管所有的bean对象都会调用配置的初始化方法,但是prototype-bean并不会调用其配置的destroy方法。所以清理工作必须由客户端进行。所以,Spring容器对prototype-bean 的管理在一定程度上类似于 new 操作,对象创建后的事情将全部由客户端处理。

session

跟request类似,但它的生命周期更长一些,是在同一次会话范围内有效,也就是说如果不关闭浏览器,不管刷新多少次,都会访问同一个bean。

application

application的作用域比session又要更广一些,session作用域是针对一个 Http Session,而application作用域,则是针对一个 ServletContext ,有点类似 singleton,但是singleton代表的是每个IOC容器中仅有一个实例,而同一个web应用中,是可能会有多个IOC容器的,但一个Web应用只会有一个 ServletContext,所以 application 才是web应用中货真价实的单例模式。

websocket

websocket 的作用范围是 WebSocket ,即在整个 WebSocket 中有效。

单例模式

定义

确保全局只有一个实例,并提供一个访问的全局访问点

优缺点

优点

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地 创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要 比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一 个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM 垃圾回收机制)。
  • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在 内存中,避免对同一个资源文件的同时写操作。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单 例类,负责所有数据表的映射处理。

缺点

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途 径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它 要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊 情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
  • 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行 测试的,没有接口也不能使用mock的方式虚拟一个对象。
  • 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单 例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

使用场景

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反 应”,可以采用单例模式,具体的场景如下:

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

最佳实践

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

角色

image-20220814221728724

单例:有一个返回唯一实例的静态方法

使用spring实现

单例

定义实例如下

@Slf4j
public class Singleton {
   Singleton(){
      log.info("生成了一个实例");
   }
   //没有判断该对象是否为null,为饿汉式
}

spring默认创建bean的方式就是单例的,我们也可以指定创建单例生成器

@Configuration
public class SingletonConfiguration {

   @Bean
   @Scope("singleton")
   public Singleton singleton(){
      return new Singleton();
   }
}

测试

@RestController
@Slf4j
public class SingletonController {

   @Resource
   Singleton singleton;
   @Resource
   Singleton singleton2;

   @GetMapping("singleton")
   public Boolean singleton(){
      System.out.println("start.");
      if(singleton == singleton2){
         log.info("obj1与obj2是相同的实例");
         return true;
      }else{
         log.error("obj1与obj2不是相同的实例");
         return false;
      }
   }
}

测试结果如下:

image-20220814212402968

原型模式

定义

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

优缺点

  • 性能优良

    原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

  • 逃避构造函数的约束

    这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。

使用场景

  • 资源优化场景

    类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

  • 性能和安全要求的场景

    通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

  • 一个对象多个修改者的场景

一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为一体,大家可以随手拿来使用

最佳实践

原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息, 建立了一个完整的个性对象。原型模式就是由一个正本可以创建多个副本的概念。可以这样理解:一个对象的产生可以不由零起步,直接从一 个已经具备一定雏形的对象克隆,然后再修改为生产需要的对象。也就是说,产生一个人, 可以不从1岁长到2岁,再到3岁……也可以直接找一个人,从其身上获得DNA,然后克隆一 个,直接修改一下就是30岁了!

角色

image-20220814221705729

  • 原型:负责定义用于复制现有实例来生成新实例的方法
  • 具体的原型:负责实现复制现有实例并生成新实例的方法
  • 使用者:使用复制实例的方法生成新实例的方法

使用spring实现

原型

定义一个抽象实例,用于复制现有实例来生成新实例

public interface Product {

   void use(String s);
}

具体原型

定义message box, 用于将字符串放入方框中并使其显示出来的类

@Slf4j
@Setter
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class MessageBox implements Product {

   private char decochar;

   @Override
   public void use(String s) {
      // TODO Auto-generated method stub
        int length = s.getBytes().length;
      StringBuilder sb = new StringBuilder();
        for(int i= 0;i<length+4;i++){
           sb.append(decochar);
        }
      log.info(sb.toString());
      log.info(decochar+" "+s+" "+decochar);
      log.info(sb.toString());
        log.info(" ");
   }
}

定义underLinePen, 用于将字符串加上下划线并使其显示出来

@Slf4j
@Setter
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class UnderLinePen implements Product {

   private char ulchar;

   @Override
   public void use(String s) {
      int length = s.getBytes().length;
      log.info("\"" + s + "\"");
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < length+2; i++) {
         sb.append(ulchar);
      }
      log.info(sb.toString());
      log.info(" ");
   }
}

使用者

定义实例生成器:

@Component
public abstract class PrototypeGenerator {


   @Lookup
   public abstract MessageBox messageBox();

   @Lookup
   public abstract UnderLinePen underLinePen();


}

原型配置器对实例进行创建使用

@Configuration
public class PrototypeConfiguration {

   @Resource
   private PrototypeGenerator prototypeGenerator;

   @Bean
   public Manager manager(){
      Manager manager = new Manager();
      UnderLinePen underLinePen = prototypeGenerator.underLinePen();
      underLinePen.setUlchar('~');
      manager.register("underLinePen", underLinePen);
      MessageBox warningBox = prototypeGenerator.messageBox();
      warningBox.setDecochar('/');
      manager.register("warningBox", underLinePen);
      MessageBox slashBox = prototypeGenerator.messageBox();
      slashBox.setDecochar('*');
      manager.register("slashBox", slashBox);
      return manager;
   }
}

测试

@RestController
public class PrototypeController {

   @Resource
   Manager manager;

   @GetMapping("prototype")
   public void prototype(String msgType, String msg){
      //生成,根据现有实例生成新实例
      Product p1 = manager.create(msgType);
      p1.use(msg);
   }
}

测试结果:

image-20220814220037710

工厂模式

定义

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

优缺点

首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需 要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创 建对象的艰辛过程,降低模块间的耦合。

其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体 的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。

再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关 心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类 决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC 连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提 条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接 案例。

最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实 现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依 赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类也没问题!

使用场景

首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
最后,可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock。

最佳实践

工厂方法模式在项目中使用得非常频繁,以至于很多代码中都包含工厂方法模式。该模 式几乎尽人皆知,但不是每个人都能用得好。熟能生巧,熟练掌握该模式,多思考工厂方法 如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模板方法模式、单例模式、 原型模式等),变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在

角色

角色图:

image-20220814162021851

  • 产品:抽象类,决定了实例所持有的接口
  • 创建者:负责生产产品的抽象类
  • 具体的产品:决定了具体的实例
  • 具体的创建者:负责生产具体的产品

使用spring实现

实例

我们指定一个工厂生产身份证,身份证实例为:

@Data
@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class IDcard extends Product {

	private int ID;
	private String owner;

	@Override
	public void use() {
		// TODO Auto-generated method stub
		System.out.println("使用" + owner +"::"+ID+ "的ID卡");
	}
	
}

这里指定scope为原型模式,方便我们使用工厂来生产多个不同的身份证。

生成器

我们为工厂指定了一个实例生成器,方便我们生成多个不同实例

@Component
public abstract class IDCardGenerator {

	@Lookup
	public abstract IDcard iDcard();
}

工厂类

我们的工厂负责为生成器生成的实例组装不同的属性。

@Service
public class IDCardFactory extends Factory {

	@Resource
	IDCardGenerator iDcardGenerator;

	private Map<Integer, String> owners = new HashMap<Integer, String>();

	@Override
	protected IDcard createProduct(int ID, String owner) {
		// TODO Auto-generated method stub
		IDcard iDcard =iDcardGenerator.iDcard();
		iDcard.setID(ID);
		iDcard.setOwner(owner);
		return iDcard;
	}

	/**
	 * 通过将IDcard的持有人保存到owners中完成注册
	 */
	@Override
	protected void registerProduct(Product product) {
		// TODO Auto-generated method stub
		owners.put(((IDcard) product).getID(), ((IDcard) product).getOwner());
	}

	public Map<Integer, String> getOwners() {
		return owners;
	}
}

测试

我们编写测试接口,进行测试:

@RestController
public class IDCardController {

   @Resource
   Factory factory;

   @GetMapping("factory")
   public void factory(){
      Product card1 = factory.create(123,"小明");
      Product card2 = factory.create(124,"小红");
      Product card3 = factory.create(125,"小刚");
      card1.use();
      card2.use();
      card3.use();
   }
}

测试结果如下:

image-20220814203617179

抽象工厂模式

定义

提供了一个可以创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类

优缺点

优点

  • 封装性,每个产品的实现类不是高层模块要关心的,它要关心的是什么?是接口,是 抽象,它不关心对象是如何创建出来,这由谁负责呢?工厂类,只要知道工厂类是谁,我就 能创建出一个需要的对象,省时省力,优秀设计就应该如此。
  • 产品族内的约束为非公开状态。生产实例过程对调用工厂类的高层模块来说是透明的,它不需要知道这个约束,具体的产品族内的约束是在工厂内实现的。

缺点

抽象工厂模式的最大缺点就是产品族扩展非常困难,为什么这么说呢?我们以通用代码 为例,如果要增加一个产品C,也就是说产品家族由原来的2个增加到3个,看看我们的程序 有多大改动吧!抽象类AbstractCreator要增加一个方法createProductC(),然后两个实现类都要 修改,想想看,这严重违反了开闭原则,而且我们一直说明抽象类和接口是一个契约。改变 契约,所有与契约有关系的代码都要修改,那么这段代码叫什么?叫“有毒代码”,——只要 与这段代码有关系,就可能产生侵害的危险!

使用场景

抽象工厂模式的使用场景定义非常简单:一个对象族(或是一组没有任何关系的对象) 都有相同的约束,则可以使用抽象工厂模式。什么意思呢?例如一个文本编辑器和一个图片 处理器,都是软件实体,但是* nix下的文本编辑器和Windows下的文本编辑器虽然功能和界 面都相同,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条 件:操作系统类型。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片 处理器。

最佳实践

一个模式在什么情况下才能够使用,是很多读者比较困惑的地方。抽象工厂模式是一个 简单的模式,使用的场景非常多,大家在软件产品开发过程中,涉及不同操作系统的时候, 都可以考虑使用抽象工厂模式,例如一个应用,需要在三个不同平台(Windows、Linux、 Android(Google发布的智能终端操作系统))上运行,你会怎么设计?分别设计三套不同 的应用?非也,通过抽象工厂模式屏蔽掉操作系统对应用的影响。三个不同操作系统上的软件功能、应用逻辑、UI都应该是非常类似的,唯一不同的是调用不同的工厂方法,由不同的 产品类去处理与操作系统交互的信息

角色

image-20220814221616171

  • 抽象产品:定义抽象工厂所生成的抽象零件和产品的接口。
  • 抽象工厂:定义用于生成抽象产品的接口
  • 委托者:仅调用抽象工厂和抽象产品的接口进行工作
  • 具体产品:负责实现抽象产品的接口
  • 具体工厂:负责实现抽象工厂的角色的接口

使用spring实现

我们实现一个工厂,可以将带有层次关系的链接集合制作成html文件

抽象产品

定义一个item,方便同意处理链接等html元素

public abstract class Item {

   protected String caption;

   public void setCaption(String caption) {
      this.caption = caption;
   }

   public abstract String makeHTML();
}

定义link,表示含有html链接的类

public abstract class Link extends Item {

   protected String url;

   public void build(String caption, String url){
      setCaption(caption);
      this.url = url;
   }

   public abstract String makeHTML();

}

定义tray,表示含有link和tray的类

public abstract class Tray extends Item {
   protected ArrayList<Item> tray = new ArrayList<Item>();

   public void build(String caption){
      setCaption(caption);
   }
    
   public void add(Item item){
      tray.add(item);
   }
}

定义page,表示html页面的类

@Slf4j
public abstract class Page {

   protected String title;
   protected String author;
   protected ArrayList<Item> content = new ArrayList<>();

   public void build(String title, String author) {
      this.title = title;
      this.author = author;
   }

   public void add(Item item) {
      content.add(item);
   }

   public void output() {
      try {
         File file = ResourceUtils.getFile("classpath:templates/" + title + ".html");
         Writer writer = new FileWriter(file);
         writer.write(this.makeHTML());
         writer.close();
         log.info(file.getName() + "编写完成");
      } catch (IOException e) {
         log.error("编写异常", e);
      }
   }

   public abstract String makeHTML();
}

抽象工厂

表示制作link,tray,page的工厂类

public abstract class Factory {

   public String getName(){
      return this.getClass().getAnnotation(FactoryName.class).value();
   }

   public abstract Link createLink(String caption, String url);

   public abstract Tray createTray(String caption);

   public abstract Page createPage(String title, String author);
}

拓展:为了方便指定工厂,我们定义注解factoryName,方便指定名称

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FactoryName {

   String value();
}

具体产品

表示link列表项的:

@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class ListLink extends Link {

	public String makeHTML(){
		return "<li><a href = \""+url+"\">"+caption+"</li>\n";
	}

}

表示tray列表项的

@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class ListTray extends Tray {

	@Override
	public String makeHTML() {
		StringBuffer buffer =  new StringBuffer();
		buffer.append("<li>\n");
		buffer.append(caption+"\n");
		buffer.append("<ul>");
		Iterator<Item> it = tray.iterator();
		while(it.hasNext()){
			Item item = (Item)it.next();
			buffer.append(item.makeHTML());
		}
		buffer.append("</ul>\n");
		buffer.append("</li>\n");
		return buffer.toString();
	}

}

表示包含列表的html页

@Component
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public class ListPage extends Page {

	@Override
	public String makeHTML() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("<html><head><title>"+title+"</title></head>\n");
		buffer.append("<body>\n");
		buffer.append("<h1>"+title+"</h1>\n");
		buffer.append("<ul>\n");
		Iterator<Item> it = content.iterator();
		while(it.hasNext()){
			Item item = (Item)it.next();
			buffer.append(item.makeHTML());
		}
		buffer.append("</ul>\n");
		buffer.append("<hr><address>"+author+"</address>");
		buffer.append("</body></html>\n");
		return buffer.toString();
	}

}

具体工厂

表示制作link,tray等列表项的工厂类

@Service
@FactoryName("listFactory")
public class ListFactory extends Factory {

   @Resource
   private ListGenerator listGenerator;

   @Override
   public Link createLink(String caption, String url) {
      // TODO Auto-generated method stub
      ListLink listLink = listGenerator.listLink();
      listLink.build(caption, url);
      return listLink;
   }

   @Override
   public Tray createTray(String caption) {
      // TODO Auto-generated method stub
      ListTray listTray = listGenerator.listTray();
      listTray.build(caption);
      return listTray;
   }

   @Override
   public Page createPage(String title, String author) {
      // TODO Auto-generated method stub
      ListPage listPage = listGenerator.listPage();
      listPage.build(title, author);
      return listPage;
   }

}

具体产品生成器

表示生成link,tray等列表项的生成器

@Component
public abstract class ListGenerator {

   @Lookup
   public abstract ListLink listLink();

   @Lookup
   public abstract ListPage listPage();

   @Lookup
   public abstract ListTray listTray();

}

委托者

定义工厂生成器

@Component
public class AbstractFactoryGenerator  {


   @Resource
   private List<Factory> factories;

   private Map<String, Factory> factoryMap = new HashMap<>();

   public Factory getFactory(String factoryName){
      return factoryMap.get(factoryName);
   }

   public boolean isSupportFactory(String name){
      return factoryMap.containsKey(name);
   }

   @PostConstruct
   public void initMap(){
      for (Factory factory: factories){
         factoryMap.put(factory.getName(), factory);
      }
   }

}

测试

具体工厂我们省略了tableFactory的编写过程,测试过程如下:

@Controller
public class AbstractFactoryController {


   @Resource
   private AbstractFactoryGenerator abstractFactoryGenerator;



   /**
    * Usage: Java Main class.name.of.ConcreteFactory
    *
    * @param factoryName
    * @return
    */
   @GetMapping("/createFactoryBean")
   public ModelAndView factory(String factoryName, String author){
      ModelAndView modelAndView = new ModelAndView();
      if(abstractFactoryGenerator.isSupportFactory(factoryName)){
         modelAndView.addObject("errorMsg", "根据factoryName获取不到对应工厂");
         modelAndView.setViewName("error");
         return modelAndView;
      }
      Factory factory = abstractFactoryGenerator.getFactory(factoryName);
      createPage(factory, author);
      modelAndView.setViewName("hello");
      return modelAndView;
   }


   private void createPage(Factory factory, String author){
      Link people = factory.createLink("人民日报", "http://www.people.com/");
      Link gmw = factory.createLink("光明日报", "http://www.gmw.cn/");

      Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
      Link jp_yahoo  = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.com/");
      Link excite = factory.createLink("Excite", "http://www.excite.com/");
      Link google = factory.createLink("Google", "http://www.google.com/");

      Tray traynews = factory.createTray("日报");
      traynews.add(people);
      traynews.add(gmw);

      Tray trayyahoo = factory.createTray("Yahoo!");
      trayyahoo.add(us_yahoo);
      trayyahoo.add(jp_yahoo);

      Tray traysearch = factory.createTray("搜索引擎");
      traysearch.add(trayyahoo);
      traysearch.add(excite);
      traysearch.add(google);

      Page page = factory.createPage("hello", author);
      page.add(traynews);
      page.add(traysearch);
      page.output();
   }
}

测试结果如下:

image-20220814211746045

建造器模式

定义

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

优缺点

  • 封装性。使用建造者模式可以使客户端不必知道产品内部组成的细节
  • 建造者独立,容易扩展
  • 便于控制细节风险。由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任 何影响。

使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可 以使用该模式。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建 造者模式非常合适。
  • 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程 中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景只能是一个补偿方 法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建 过程,本身已经违反设计的最初目标。

最佳实践

在使用建造者模式的时候考虑一下模板方法模式,别孤立地思考一个模式, 僵化地套用一个模式会让你受害无穷

角色

image-20220814221542953

  • 建造者:负责定义用于生成实例的接口
  • 具体建造者:实现建造者的接口,定义了实例生成时实际被调用的方法
  • 监工:使用建造者的接口生成实例。只调用建造者中被定义的方法

使用spring实现

我们接下来使用建造者模式编写一个含有标题、字符串和条目内容的文档

建造者

定义文档结构方法的抽象类

public interface Builder {

   void makeTitle(String title);

   void makeString(String str);

   void makeItems(String[] items);

   void close();

   String getResult();
}

监工

通过一个监工来编写一个文档

@Service
@Setter
public class Director {

   private Builder builder;
   
   public void construct(){
      builder.makeTitle("Greeting");
      builder.makeString("从早上至上午");
      builder.makeItems(new String[]{
            "早上好。",
            "下午好。",
      });
      builder.makeString("晚上");
      builder.makeItems(new String[]{
            "早上好。",
            "晚安。",
            "再见。",
      });
      builder.close();
   }
}

具体建造者

定义建造者名称

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BuilderName {
   String value();
}

使用纯文档来编写文档

@Component
@BuilderName("textBuilder")
public class TextBuilder implements Builder {

   private StringBuffer buffer = new StringBuffer();

   @Override
   public void makeTitle(String title) {
      // TODO Auto-generated method stub
      buffer.append("======================\n");
      buffer.append("[" + title + "]\n");
      buffer.append("\n");

   }

   @Override
   public void makeString(String str) {
      // TODO Auto-generated method stub
      buffer.append("[]" + str + "\n");
      buffer.append("\n");
   }

   @Override
   public void makeItems(String[] items) {
      // TODO Auto-generated method stub
      for (int i = 0; i < items.length; i++) {
         buffer.append("   ." + items[i] + "\n");
      }
      buffer.append("\n");
   }

   @Override
   public void close() {
      // TODO Auto-generated method stub
      buffer.append("================\n");
   }

   public String getResult() {
      return buffer.toString();
   }
}

建造者生成器

将所有建造者放到生成器中,方便根据名称取用

@Component
public class BuilderGenerator {
   @Resource
   private List<Builder> builders;
   private Map<String, Builder> builderMap = new HashMap<>();


   public Builder getBuilder(String builderName){
      return builderMap.get(builderName);
   }

   @PostConstruct
   public void initBuilder(){
      for(Builder builder: builders){
         builderMap.put(builder.getClass().getAnnotation(BuilderName.class).value(), builder);
      }
   }
}

测试

@RestController
public class BuilderController {


   @Resource
   private BuilderGenerator builderGenerator;
   @Resource
   private Director director;

   /**
    *  textBuilder 编写纯文本文档
    *  HTMLBuilder 编写HTML文档
    * @param builderName
    * @return
    */
   @GetMapping("builder")
   public String builder(String builderName){
      Builder builder = builderGenerator.getBuilder(builderName);
      director.setBuilder(builder);
      director.construct();
      return builder.getResult();
   }
}

测试结果如下
image-20220814221028833!

0

评论区