最近review代码,感觉工厂方法使用的不准确,正好以此为例聊一下工厂方法的演进。实例初始需求 假设我们有一个需求,需要根据不同的信号做不同的事情,如做饭、吃饭。 在此需求基础上,我们用Go实现比较简单,使用经典的简单工厂即可:创建一个interface,包含参数检查、执行动作创建做饭、吃饭类,实现interface中的两个函数 因为信号不同行为不同,根据查表法与switch有什么区别?,为后期扩展方便,我们选择switch方式。需求进化 后面我们发现需求变了,要增加洗碗、拖地,而且这两个的操作和做饭也很相似。 这种情况下我们可以选择的方案有:复用做饭类,在里面通过if判断是洗碗还是拖地做饭、吃饭、洗碗、拖地完全独立,相互之间没有交集 我们肯定选择方案2,使用下面两个技巧使代码高内聚、低耦合使用基类:如洗碗、拖地都需要用水清扫,这些相同操作,在基类中实现,洗碗、拖地类继承基类提取公因子,将各个类共同的功能放到框架中,如在做之前都吼了一嗓子,我不想工作简单工厂实现 关于工厂模式,大家可以看一下我的这篇文章Go设计模式(7)工厂模式。简单工厂方法的UML图如下: 对于初始需求的代码实现如下所示:packagemainimportfmtAuthor:JasonPangDescription:typeLifeinterface{CheckParams()error参数检查Do()error执行动作}typeCookstruct{}Author:JasonPangDescription:做饭参数检查receivercreturnerrorfunc(cCook)CheckParams()error{fmt。Println(cook检查参数,食材准备完毕)returnnil}Author:JasonPangDescription:开始做饭receivercreturnerrorfunc(cCook)Do()error{fmt。Println(不想工作)fmt。Println(开始做饭)returnnil}typeEatstruct{}Author:JasonPangDescription:吃饭参数检查receivercreturnerrorfunc(cEat)CheckParams()error{fmt。Println(eat检查参数,饭已做好,碗筷放好)returnnil}Author:JasonPangDescription:开始吃饭receivercreturnerrorfunc(cEat)Do()error{fmt。Println(不想工作)fmt。Println(开始吃饭)returnnil}Description:简单工厂typeFactorystruct{}func(simpleFactory)create(extstring)Life{switchext{casecook:returnCook{}caseeat:returnEat{}}returnnil}funcmain(){简单工厂使用代码fmt。Println(简单工厂)factory:Factory{}life:factory。create(cook)iflife!nil{life。CheckParams()life。Do()}}简单工厂演进实现 需求演变之后,代码实现如下:packagemainimportfmtAuthor:JasonPangDescription:typeLifeinterface{CheckParams()error参数检查Do()error执行动作}Author:JasonPangDescription:基类typeBaseLifestruct{}Author:JasonPangDescription:参数检查receivercreturnerrorfunc(cBaseLife)CheckParams()error{fmt。Println(通用参数检查)returnnil}Author:JasonPangDescription:开始做饭receivercreturnerrorfunc(cBaseLife)Do()error{fmt。Println(用水处理)returnnil}typeCookstruct{}Author:JasonPangDescription:做饭参数检查receivercreturnerrorfunc(cCook)CheckParams()error{fmt。Println(cook检查参数,食材准备完毕)returnnil}Author:JasonPangDescription:开始做饭receivercreturnerrorfunc(cCook)Do()error{fmt。Println(开始做饭)returnnil}typeEatstruct{}Author:JasonPangDescription:吃饭参数检查receivercreturnerrorfunc(cEat)CheckParams()error{fmt。Println(eat检查参数,饭已做好,碗筷放好)returnnil}Author:JasonPangDescription:开始吃饭receivercreturnerrorfunc(cEat)Do()error{fmt。Println(开始吃饭)returnnil}Author:JasonPangDescription:洗碗typeWashstruct{BaseLife}typeMopstruct{BaseLife}func(cMop)CheckParams()error{fmt。Println(mop检查参数,拖把是否存在)returnnil}Description:简单工厂typeFactorystruct{}func(simpleFactory)create(extstring)Life{switchext{casecook:returnCook{}caseeat:returnEat{}casewash:returnWash{}casemop:returnMop{}}returnnil}funcEchoBeforeDo(){fmt。Println(不想工作)}funcmain(){简单工厂使用代码fmt。Println(简单工厂)factory:Factory{}life:factory。create(mop)iflife!nil{life。CheckParams()EchoBeforeDo()life。Do()}} 输出:myprojectgorunmain。go简单工厂cook检查参数,食材准备完毕不想工作开始做饭myprojectgorunmain。go简单工厂eat检查参数,饭已做好,碗筷放好不想工作开始吃饭myprojectgorunmain。go简单工厂通用参数检查不想工作用水处理myprojectgorunmain。go简单工厂mop检查参数,拖把是否存在不想工作用水处理 大家可以看到,这种方案即保证了各个操作之间的独立,又复用了共同代码(通过基类和提取公因子)。总结 使用工厂方法,有两个检验标准具体产品类不应该相互之间关联产品类里也不应该有相同的代码 随着对业务的理解,区分出变与不变的内容,不变的内容需要整合到框架中,不应该在各个产品类里。 产品类只需关注自己的逻辑,按照接口要求处理输入和返回值。这样今后即使有新功能接入,开发者也不需要关心整体框架,上手速度快、出问题的概率低。 如果有默认的逻辑操作能跑通整个流程,最好有一个基类实现这个逻辑,这样就能最大程度的进行复用。 开发过程中需要随着业务的变化和自己对业务的理解不断重构代码,这样才能让代码不成为屎山。但很多同学可能不敢重构,怕引起更多问题。其实这就和单元测试、自动化测试等关联起来了,只要质量保障的好,才能更放心的修改。我认为质量保障就是内功了,需要不断的坚持、不松懈,需要团队有很强的执行力,这是很难短时间被学去的,这便是护城河。 代码位置:https:github。comshidawuhenasapblobmastercontrollerdesignfactory。go最后 大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫) 我的个人博客为:https:shidawuhen。github。io 往期文章回顾:设计模式招聘思考存储算法系列读书笔记小工具架构网络Go语言