阔别已久了的博客,我又回来。为什么又好长时间没写了,因为最近产品给我的需求稍微有点多,加上自己给自己加的需求,导致经常早九晚二,没空输出了。最近我司项目加入了多渠道快速打包和热更新(老项目添加新技术真的是困难重重,花费了较长时间),并研究了下 Jenkins 持续集成打包(但是由于公司服务器不给我用,先割了段时间)。
最近在项目中写bug的时候发现,项目中的公共Dialog管理类是这样的:
导致每一次增加一种类型的 Dialog 就要增加一个构造函数来实现,久而久之这个类属性也越来越多,构造参数的种类也越来越多,这个类的初衷是为了封装系统AlterDialog的使用,让一些相同样式的Dialog可以用最简单的样式创建,无奈UI样式随着产品的迭代,越来越多,这种单纯的封装有点让人力不从心了。
本文将介绍一种简单的设计模式「建造者模式」。也许它可以很好的解决生产中类似上述描述的情况。
建造者模式的组成部分
我所理解的设计模式思想,本身就是用于解决重复代码封装的一种思想。是前辈们给我们留下的经验。而「书本中的建造者模式」是下边的下边这样子的:
可以看出设计模式由四部分组成:
- Product: 产品类所描述的是我们最终想要的到的结果统称,例如一个封装好的带有多选按钮列表的 Dialog。
- Builder: 建造者的接口定义,他定义了建造某种产品类需要的步骤,但不提供实现。
- ConcreteBuilder: 建造者的实现类,继承自 Builder 提供一种创建类型的具体步骤,是我们封装的拓展对象,比如项目之前只有双按钮的对话框,现在我们有一种带多选功能的对画框需要创建,我们就可以新建一个 ConcreteBuilder 继承自Builder,来完成对多选框Dialog的创建工作,而无序改动前两者。
- Director: Builder 类的创建者,由他来为各种 Builder 来赋值属性,Director隔离了建造者实现类与实际客户端创建,让实际对象的创建只关注于对象的属性。
建造者模式的例子
下面通过一个抽象派的 Dota 英雄创建过程,来看下上述四部分是怎么工作的:
首先定一个 DotaHero
类这个类是我们最终的产品类也就是 Product,他可以理解为一个超类,具体实现决定于建造者如何建造。
public class DotaHero {
//英雄分类 智力 力量 和 敏捷
public static final int HERO_INTELLECTUAL = 1;
public static final int HERO_POWEER = 2;
public static final int HERO_AGLIE = 3;
@IntDef(value = {HERO_AGLIE, HERO_POWEER, HERO_INTELLECTUAL})
public @interface HeroType {
}
//英雄属性
private String heroName;
private String heroDes;
private int heroType;
private HashMap<String, String> heroSkills;
//施放置顶技能
public void executeSkill(String key) {
if (heroSkills.containsKey(key)) {
System.out.println("施放 " + key + " 技能: " + heroSkills.get(key));
}
}
..... //省略很多的get set
}
复制代码
DotaHero 是建造者建造的目标,这里定义的属性和方法是最全的,不同的建造者,通过不同的构建步骤,为属性赋值得到最终的不同英雄。
下面我们来定一个一个抽象的建造者创建类,该类定义了所有建造者需要的属性,实现类可以任意扩展。
public abstract class HeroBuilder {
//最终建造的对象
protected DotaHero hero;
public HeroBuilder() {
//这里在构造函数中就初始化了对象,其实最好的方法是将其放到build方法中。
hero = new DotaHero();
}
public HeroBuilder setHeroName(String name) {
hero.setHeroName(name);
return this;
}
public HeroBuilder setHeroDes(String des) {
hero.setHeroDes(des);
return this;
}
public HeroBuilder addHeroSkill(String keyName, String skill) {
hero.addHeroSkill(keyName, skill);
return this;
}
//定义了英雄类型的抽象方法,需要子类去实现
protected abstract HeroBuilder setHeroType();
public DotaHero build() {
return hero;
}
}
复制代码
接下来我们来定义Dota中的英雄建造者,大家都知道,Dota 中英雄有 智力,敏捷,力量三种,所以我们这里定义三种建造者用来构建者三种类型的英雄。
//智力
public class AglieHeroBuilder extends HeroBuilder {
@Override
protected HeroBuilder setHeroType() {
hero.setHeroType(DotaHero.HERO_AGLIE);
return this;
}
}
//敏捷
public class IntellectualHeroBuilder extends HeroBuilder {
@Override
protected HeroBuilder setHeroType() {
hero.setHeroType(DotaHero.HERO_INTELLECTUAL);
return this;
}
}
//力量
public class PowerHeroBuilder extends HeroBuilder {
@Override
protected HeroBuilder setHeroType() {
hero.setHeroType(DotaHero.HERO_POWEER);
return this;
}
}
复制代码
上述列举了三种建造者,这里只做了抽象方法的实现,实际生产中,子类可以根据需要拓展独有属性。
完成上述工作后,我们就已经能够使用建造者来创建我们需要的对象了。但建造者模式中推荐我们将实际对象的生产的工作不要暴露给客户端,需要通过创建者(创建建造者的类)来隔离建造者和客户端,这样客户端只需要声明我需要一个什么样的建造者,来创建一个什么样的对象就可以了。这样当以后出现了新的建造者类型的时候,客户端仍旧只需要关注上述两点。
下面我们来看下创建者的定义:
public class Director {
private HeroBuilder builder;
//构造函数1 直接在外界定义好某类型的 Builder 传入
public Director(@NonNull HeroBuilder builder) {
this.builder = builder;
}
public DotaHero getAHero() {
if (builder != null) {
return builder.build();
}
return null;
}
}
复制代码
上述代码暴露的个构造方法来创建建造者。他目前所做的工作就是获取建造者或者根据类型创建建造者,通过getAHero方法来获得一个生产对象。
何时该应用建造者模式
通过上述的例子我们可以很好的理解,第一部分建造者模式的各组成部分的作用以及意义。那么在实际生产过程中我们何时该应用建造者模式呢?
在以下情况下可以使用建造者模式:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 建造者对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
建造者模式的意义在于,建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
##建造者模式的优缺点
建造者模式的优点:
在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。
增加新的具体建造者无须修改原有类库的代码,创建者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。
建造者模式的缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,会导致具体建造者数量变得很多。
总结
通过上文的叙述相信大家已经知道什么是建造者模式了,我们也清楚了建造者模式定义步骤,但是实际开发中,如果一个产品类,变化的范围仅仅是属性不同或者构造顺序不同,那么一个建造者实现类就可以完成工作,那么我们可以省略后两个角色,也就是说 Builder 的类兼职完成步骤2和步骤3。
学习设计模式,笔者认为最好的学习方法就是应用,本文中列举的例子可能过于简单,离掌握还差的远,下一片文章将会带领大家了解我对我司项目中 Dialog 创建封装的过程,从实践中来理解。
参考链接: