文章摘要(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的生命周期。
角色
单例:有一个返回唯一实例的静态方法
使用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;
}
}
}
测试结果如下:
原型模式
定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
优缺点
-
性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
-
逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
使用场景
-
资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
-
性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
-
一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为一体,大家可以随手拿来使用
最佳实践
原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息, 建立了一个完整的个性对象。原型模式就是由一个正本可以创建多个副本的概念。可以这样理解:一个对象的产生可以不由零起步,直接从一 个已经具备一定雏形的对象克隆,然后再修改为生产需要的对象。也就是说,产生一个人, 可以不从1岁长到2岁,再到3岁……也可以直接找一个人,从其身上获得DNA,然后克隆一 个,直接修改一下就是30岁了!
角色
- 原型:负责定义用于复制现有实例来生成新实例的方法
- 具体的原型:负责实现复制现有实例并生成新实例的方法
- 使用者:使用复制实例的方法生成新实例的方法
使用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);
}
}
测试结果:
工厂模式
定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
优缺点
首先,良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需 要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创 建对象的艰辛过程,降低模块间的耦合。
其次,工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体 的工厂类或扩展一个工厂类,就可以完成“拥抱变化”。
再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关 心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类 决定的。在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用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。
最佳实践
工厂方法模式在项目中使用得非常频繁,以至于很多代码中都包含工厂方法模式。该模 式几乎尽人皆知,但不是每个人都能用得好。熟能生巧,熟练掌握该模式,多思考工厂方法 如何应用,而且工厂方法模式还可以与其他模式混合使用(例如模板方法模式、单例模式、 原型模式等),变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在
角色
角色图:
- 产品:抽象类,决定了实例所持有的接口
- 创建者:负责生产产品的抽象类
- 具体的产品:决定了具体的实例
- 具体的创建者:负责生产具体的产品
使用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();
}
}
测试结果如下:
抽象工厂模式
定义
提供了一个可以创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
优缺点
优点
- 封装性,每个产品的实现类不是高层模块要关心的,它要关心的是什么?是接口,是 抽象,它不关心对象是如何创建出来,这由谁负责呢?工厂类,只要知道工厂类是谁,我就 能创建出一个需要的对象,省时省力,优秀设计就应该如此。
- 产品族内的约束为非公开状态。生产实例过程对调用工厂类的高层模块来说是透明的,它不需要知道这个约束,具体的产品族内的约束是在工厂内实现的。
缺点
抽象工厂模式的最大缺点就是产品族扩展非常困难,为什么这么说呢?我们以通用代码 为例,如果要增加一个产品C,也就是说产品家族由原来的2个增加到3个,看看我们的程序 有多大改动吧!抽象类AbstractCreator要增加一个方法createProductC(),然后两个实现类都要 修改,想想看,这严重违反了开闭原则,而且我们一直说明抽象类和接口是一个契约。改变 契约,所有与契约有关系的代码都要修改,那么这段代码叫什么?叫“有毒代码”,——只要 与这段代码有关系,就可能产生侵害的危险!
使用场景
抽象工厂模式的使用场景定义非常简单:一个对象族(或是一组没有任何关系的对象) 都有相同的约束,则可以使用抽象工厂模式。什么意思呢?例如一个文本编辑器和一个图片 处理器,都是软件实体,但是* nix下的文本编辑器和Windows下的文本编辑器虽然功能和界 面都相同,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条 件:操作系统类型。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片 处理器。
最佳实践
一个模式在什么情况下才能够使用,是很多读者比较困惑的地方。抽象工厂模式是一个 简单的模式,使用的场景非常多,大家在软件产品开发过程中,涉及不同操作系统的时候, 都可以考虑使用抽象工厂模式,例如一个应用,需要在三个不同平台(Windows、Linux、 Android(Google发布的智能终端操作系统))上运行,你会怎么设计?分别设计三套不同 的应用?非也,通过抽象工厂模式屏蔽掉操作系统对应用的影响。三个不同操作系统上的软件功能、应用逻辑、UI都应该是非常类似的,唯一不同的是调用不同的工厂方法,由不同的 产品类去处理与操作系统交互的信息
角色
- 抽象产品:定义抽象工厂所生成的抽象零件和产品的接口。
- 抽象工厂:定义用于生成抽象产品的接口
- 委托者:仅调用抽象工厂和抽象产品的接口进行工作
- 具体产品:负责实现抽象产品的接口
- 具体工厂:负责实现抽象工厂的角色的接口
使用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();
}
}
测试结果如下:
建造器模式
定义
将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示
优缺点
- 封装性。使用建造者模式可以使客户端不必知道产品内部组成的细节
- 建造者独立,容易扩展
- 便于控制细节风险。由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任 何影响。
使用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可 以使用该模式。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建 造者模式非常合适。
- 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程 中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景只能是一个补偿方 法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建 过程,本身已经违反设计的最初目标。
最佳实践
在使用建造者模式的时候考虑一下模板方法模式,别孤立地思考一个模式, 僵化地套用一个模式会让你受害无穷
角色
- 建造者:负责定义用于生成实例的接口
- 具体建造者:实现建造者的接口,定义了实例生成时实际被调用的方法
- 监工:使用建造者的接口生成实例。只调用建造者中被定义的方法
使用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();
}
}
测试结果如下
!
评论区