设计模式(三):生成器模式

这是设计模式系列文章的第三篇

之前两篇的阅读效果不是很好,我一度怀疑这种题材的文章不受大家欢迎,直到前两天我面试了一个小姐姐...

面试过程中和小姐姐聊起她在上家公司做过的项目,其中有一个功能,根据小姐姐的描述,我第一感觉应该用生成器模式来实现

小姐姐说她并没有用生成器模式,就是简单的硬编码

我问她为什么不使用生成器模式实现的时候,小姐姐的一句话突破了我的认知下线

小姐姐说:我不知道什么是生成器模式,我不打算做架构师,没必要学设计模式

原来她认为设计模式只有在做架构设计的时候才会用到,跟普通程序员没有关系

我觉得小姐姐的观点存在严重问题,设计模式是程序员的基本技能,每个程序员都应该掌握并灵活应用

良好的代码设计不仅可以让代码重复性更高,还能使代码更易读从而降低代码后期的维护成本,最重要的是可以提高系统的可靠性

今天,我们就使用生成器模式来实现小姐姐的需求

实际案例

我们先来看一下这个小姐姐的项目的具体需求

根据用户近期的消费金额、消费次数、浏览商品类型、商品价格区间等一些属性,生成用户画像。根据画像分析用户行为,实现精准营销或刺激消费等。

当然,不同的业务关注的角度也不同。比如精准营销业务关注的是用户近半年的数据,而且以消费数据为主;刺激消费业务关注的是用户近一个月的数据,而且以常打开的商品为主

从编程角度把需求提炼一下,大概就是以下两点:

提供一个用户对象,这个对象包括用户名、消费金额、消费次数、浏览商品类型、商品价格区间等属性

根据这个对象进行一些业务处理

我们先来看一下小姐姐当初是怎么实现这个需求的

小姐姐的代码是在精准营销和刺激消费的业务逻辑里面,分别创建了一个User对象。

两个业务中创建User对象的逻辑基本一样,只有在获取近期消费数据时稍有差别。一个是获取近半年的数据,另一个是获取近一个月的数据

这样的硬编码是把User对象的创建过程,嵌入到了其他业务逻辑里面,这就造成一些问题

问题一:代码重用性降低

User对象的创建逻辑基本一样,但是写了两遍。如果后期加入新的业务,User对象的创建逻辑还要再写一遍,代码重用性太低

问题二:维护成本增大

示例中的伪代码模拟的比较简单,实际上User对象的创建过程非常复杂,需要查询各种数据并且对数据进行过滤、分类、整理,代码可能有几百行

精准营销或刺激消费的业务逻辑也是非常复杂的,把两块复杂的逻辑写到一块,后期阅读或维护代码的成本将几何倍的增长

问题三:代码耦合度增加

将两块业务逻辑写到一起,其中不免会共享一些逻辑。

如果后期想对共享的逻辑进行修改,让其仅对其中一方生效,代码的修改是很不友好的,很容易造成另一方的逻辑漏洞

我们可以尝试使用生成器模式来解决这些问题

生成器模式

生成器模式定义

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

换成大白话理解就是:一个复杂的对象,它的创建过程和使用过程要分开。对于对象的使用者来说,我只需要告诉创建者我需要使用这个复杂对象,至于这个复杂对象是怎么创建的,不关我事(ps:有点渣男的味道)

生成器模式使用场景

在创建一个对象时,同时满足以下条件,可以使用生成器模式

对象的创建过程非常复杂

对象的创建步骤固定

不同的调用者获得的对象不完全相同

如果需要创建的对象不复杂,这时候是没必要使用生成器模式的。因为生成器模式本身的代码实现有一点复杂,使用它成本有点高,还不如简单的硬编码

如果对象的创建步骤不固定,也不推荐使用生成器模式。

假如在小姐姐的项目中,如果精准营销需要用户的消费数据,不需要浏览商品数据;刺激消费需要用户的浏览商品数据,不需要消费数据。

User对象的创建步骤就是

两个业务创建User对象的步骤是不一样的,这时候不适合使用生成器模式

如果所有的调用者需要的对象完全一样,也不需要使用生成器模式。

假如小姐姐的需求中,两个业务关注的消费数据都是近一个月的,对消费数据和商品数据的关注度也是一样的,就不需要使用生成器模式,只需要把User对象的创建过程进行单独的封装,两个业务直接调用即可

生成器模式实战

我们先来看一下生成器模式的架构

套用到我们需求中,User对象的创建就是下面这个样子

下面使用代码实现小姐姐的需求,首先定义我们要创建的对象,也就是User类

publicclassUser{

privateString nickname;

privateintpayCnt;

privateintpayAmt;

privateList productType;

privateList amtInterval;

publicvoidsetNickname(String nickname){

this.nickname = nickname;

}

publicvoidsetPayCnt(intpayCnt){

this.payCnt = payCnt;

}

publicvoidsetPayAmt(intpayAmt){

this.payAmt = payAmt;

}

publicvoidsetProductType(List<String> productType){

this.productType = productType;

}

publicvoidsetAmtInterval(List<String> amtInterval){

this.amtInterval = amtInterval;

}

}

第二步,编写构造接口定义创建User对象需要的步骤,并提供返回User对象的方法

publicinterfaceIUserBuilder{

// 构建用户昵称

StringbuildNicaname();

// 构建用户消费次数,days代表最近天数

intbuildPayCnt();

// 构建用户消费金额,days代表最近天数

intbuildPayAmt();

// 构建用户经常浏览商品类型

ListbuildProductType();

// 构建用户经常浏览商品价格区间

ListbuildAmtInterval();

// 获取user对象

UsergetUser();

}

第三步,编写构造接口的具体实现类,重写每一个方法,编写每一个方法的具体实现逻辑。

publicclassUserBuilderimplementsIUserBuilder{

privateString days;

publicUserBuilder(String days){

this.days = days;

}

@Override

publicStringbuildNicaname(){

String nicaname ="赫连小伍";

System.out.println("查询用户昵称为:"+ nicaname);

returnnicaname;

}

@Override

publicintbuildPayCnt(){

intpayCnt =0;

if("30".equals(days)) {

payCnt =1;

}else{

payCnt =10;

}

System.out.println("查询用户近"+ days +"天的消费笔数为:"+ payCnt);

returnpayCnt;

}

@Override

publicintbuildPayAmt(){

intpayAmt =0;

if("30".equals(days)) {

payAmt =2;

}else{

payAmt =100;

}

System.out.println("查询用户近"+ days +"天的消费金额为:"+ payAmt);

returnpayAmt;

}

@Override

publicListbuildProductType(){

List list =newArrayList<>();

list.add("增发剂");

list.add("格子衫");

System.out.println("查询用户浏览的商品类型为:"+ list);

returnlist;

}

@Override

publicListbuildAmtInterval(){

List list =newArrayList<>();

list.add("1-9");

list.add("2-10");

System.out.println("查询用户浏览的商品价格区间为:"+ list);

returnlist;

}

@Override

publicUsergetUser(){

User user =newUser();

user.setNickname(this.buildNicaname());

user.setPayCnt(this.buildPayCnt());

user.setPayAmt(this.buildPayAmt());

user.setProductType(this.buildProductType());

user.setAmtInterval(this.buildAmtInterval());

returnuser;

}

}

第四步,编写Director类,对精准营销和刺激消费两块业务分别提供对应的获取User的方法。这里为了方便调用,方法全部采用static的

publicclassDirector{

// 为精准营销提供获取User的方法

publicstaticUsergetJzyxUser(){

IUserBuilder userBuilder =newUserBuilder("360");

returnuserBuilder.getUser();

}

// 为刺激消费提供获取User的方法

publicstaticUsergetCjxfUser(){

IUserBuilder userBuilder =newUserBuilder("30");

returnuserBuilder.getUser();

}

}

最后一步,模拟精准营销和刺激消费的业务,分别获取对应的User对象

publicstaticvoidmain(String[] args){

// 模拟精准营销业务逻辑

User jzyxUser = Director.getJzyxUser();

System.out.println("精准营销获得的User对象为:"+ jzyxUser);

System.out.println("开始精准营销的业务逻辑");

// 模拟刺激消费业务逻辑

User cjxfUser = Director.getCjxfUser();

System.out.println("刺激消费获得的User对象为:"+ cjxfUser);

System.out.println("开始刺激消费的业务逻辑");

}

这就用生成器模式实现了小姐姐的需求

对于精准营销或刺激消费的业务逻辑来说,它们不用再关心User对象的创建过程,可以更专注于自身的业务逻辑,无论是代码阅读或后期维护都更方便

总结

生成器模式也被称作创建者模式或建造者模式,它属于设计模式三大类型中的创建型模式

与工厂模式相比,生成器模式更善于处理创建步骤固定的复杂对象。它与工厂模式并没有很明显的界限,在许多设计初期,大部分程序员都习惯用工厂方法模式来构建代码,随着业务变得复杂,代码也会不断的重构。代码架构也逐渐的演变成抽象工厂模式、生成器模式

生成器模式也不能频繁的使用,如果项目的内部变化复杂,可能会导致需要定义很多具体生成器类来实现这种变化,导致系统变得很庞大

每一种设计模式都有利有弊,权衡利弊后找出适合自己项目的模式才会使代码变得更 “完美”

-- 以上内容来自公众号赫连小伍,转载请注明出处

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

推荐阅读更多精彩内容