设计模式-建造者模式

image

设计模式-建造者模式

阔别已久了的博客,我又回来。为什么又好长时间没写了,因为最近产品给我的需求稍微有点多,加上自己给自己加的需求,导致经常早九晚二,没空输出了。最近我司项目加入了多渠道快速打包和热更新(老项目添加新技术真的是困难重重,花费了较长时间),并研究了下 Jenkins 持续集成打包(但是由于公司服务器不给我用,先割了段时间)。

最近在项目中写bug的时候发现,项目中的公共Dialog管理类是这样的:

image

导致每一次增加一种类型的 Dialog 就要增加一个构造函数来实现,久而久之这个类属性也越来越多,构造参数的种类也越来越多,这个类的初衷是为了封装系统AlterDialog的使用,让一些相同样式的Dialog可以用最简单的样式创建,无奈UI样式随着产品的迭代,越来越多,这种单纯的封装有点让人力不从心了。

本文将介绍一种简单的设计模式「建造者模式」。也许它可以很好的解决生产中类似上述描述的情况。

建造者模式的组成部分

我所理解的设计模式思想,本身就是用于解决重复代码封装的一种思想。是前辈们给我们留下的经验。而「书本中的建造者模式」是下边的下边这样子的:

image

可以看出设计模式由四部分组成:

  1. Product: 产品类所描述的是我们最终想要的到的结果统称,例如一个封装好的带有多选按钮列表的 Dialog。
  2. Builder: 建造者的接口定义,他定义了建造某种产品类需要的步骤,但不提供实现。
  3. ConcreteBuilder: 建造者的实现类,继承自 Builder 提供一种创建类型的具体步骤,是我们封装的拓展对象,比如项目之前只有双按钮的对话框,现在我们有一种带多选功能的对画框需要创建,我们就可以新建一个 ConcreteBuilder 继承自Builder,来完成对多选框Dialog的创建工作,而无序改动前两者。
  4. 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方法来获得一个生产对象。

何时该应用建造者模式

通过上述的例子我们可以很好的理解,第一部分建造者模式的各组成部分的作用以及意义。那么在实际生产过程中我们何时该应用建造者模式呢?

在以下情况下可以使用建造者模式:

  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  • 建造者对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

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

建造者模式的优缺点

建造者模式的优点:

  1. 在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。

  3. 增加新的具体建造者无须修改原有类库的代码,创建者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。

建造者模式的缺点:

  1. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  2. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,会导致具体建造者数量变得很多。

总结

通过上文的叙述相信大家已经知道什么是建造者模式了,我们也清楚了建造者模式定义步骤,但是实际开发中,如果一个产品类,变化的范围仅仅是属性不同或者构造顺序不同,那么一个建造者实现类就可以完成工作,那么我们可以省略后两个角色,也就是说 Builder 的类兼职完成步骤2和步骤3。

学习设计模式,笔者认为最好的学习方法就是应用,本文中列举的例子可能过于简单,离掌握还差的远,下一片文章将会带领大家了解我对我司项目中 Dialog 创建封装的过程,从实践中来理解。

参考链接:

建造者模式

设计模式之婵 第2版

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容