零:前言
工厂模式的核心思想:定义一个创建对象的接口,让其子类自己决定实例化哪一个实体类,工厂模式使其创建过程延迟到子类进行。
这类设计模式的特点是:
首先,是设计一个顶级接口,可以将其看做是一个"产品的需求列表",在这里就称为"需求接口"
然后,实现这个顶级接口的结构体,即根据"需求列表"绘制出真正的投入生产的"图纸",称之为“图纸类”
最后,创建这个结构体的实例,即将"图纸"变现成"产品",称之为“产品实例”。
其中,图纸的实例化则是由"工厂"来完成,称之为"工厂接口"/"工厂类"/"工厂函数",所以具体怎样去实现这个结构体,以及如何通过这个结构体创建实例,则衍生出不同的模式,也就对应不同的"工厂模式"
说明:
在golang中,没有类的概念,取而代之的是结构体; 但是考虑到我们熟知的java/c++中得类的感念,我会在文字解释中使用"类"这个词,而在代码中使用"结构体",实际二者等同。
参考:https://github.com/senghoo/golang-design-pattern
一:简单工厂模式( Simple Factory Pattern )
1. 核心思想
首先,定义需求接口,
然后,定义各种特色性结构体,即图纸类
最后,定义工厂函数,根据要求生产出对应的产品实例,即实例化对应的结构体
2. 样例
//1.首先定义基接口,名叫API type API interface { Say(name string) string } //3.定义工厂:根据入参定制出对应的产品实例 func SimpleFactory(t int32) API { if t == 1 { return &hiAPI{} } else if t == 2 { return &helloAPI{} } return nil } //2.1 定义产品1对应的结构体,需要实现基接口 type hiAPI struct{} func (*hiAPI) Say(name string) string { return fmt.Sprintf("Hi, %s", name) } //2.2 定义产品2对应的结构体,需要实现基接口 type helloAPI struct{} func (*helloAPI) Say(name string) string { return fmt.Sprintf("Hello, %s", name) } func TestSimpleFactory(){ //TestType1 test get hiapi with factory api := SimpleFactory(1) res1 := api.Say("Tom") fmt.Println("the res1:", res1) api = SimpleFactory(2) res2 := api.Say("Tom") fmt.Println("the res2:", res2) }
3. 小小结
一个需求列表,多中图纸,根据不同的要求让工厂生产不同的产品。
二:工厂方法模式(Factory Method Pattern)
1. 核心思想
工厂方法模式使用子类的方式延迟生成对象到子类中实现,子类的生成方式采用匿名嵌入式来实现
具体的实现步骤为:
1.定义2个顶级接口: 需求接口 和 工厂接口;
2.定义基类,部分实现需求接口要求的函数。 这个基类的作用是将图纸类需要实现的接口函数中,雷同的部分抽象出来放到一个结构体中,作为各大图纸类的"基";
3.定制图纸类,通过匿名嵌入的方式继承基类,同时"特色"的实现需求接口要求的剩下的函数,如此一来特色类才真正实现了基接口;
4.定制生产特色类的工厂类,实现了工厂接口
5.使用:根据特色类实例,一方面可以调用基接口部分的通用函数,另一方面可以调用自己的特色函数
2. 样例
//1.定义顶级需求接口,名叫"操作符"(Operator),要求3个方法 type Operator interface { SetA(int) SetB(int) Result() int } //2. 定义工厂接口,名叫"操作符工厂"(OperatorFactory),要求1个"生产"函数 type OperatorFactory interface { Create() Operator } //3.定义基结构体: OperatorBase 并不是Operator 接口实现的类,他只实现了其中2个公共方法 type OperatorBase struct { a, b int } func (o *OperatorBase) SetA(a int) { o.a = a } func (o *OperatorBase) SetB(b int) { o.b = b } //4. 定制特色结构体 和 对应的工厂结构体,即"具体的操作符" 和 "具体操作符的工厂", 有如下的实现特点: // 首先,产品结构体中封装基结构体的同名指针变量,所以可以认为他实现了OperatorBase类型的SetA和SetB方法 // 然后,各家自己实现了Result()方法 // 最后,一综合,他算是实现了最底层的Operator接口 //4.1加法操作符: PlusOperator 和 该操作符的生产类:PlusOperatorFactory type PlusOperator struct { *OperatorBase } func (o PlusOperator) Result() int { return o.a + o.b } type PlusOperatorFactory struct{} func (PlusOperatorFactory) Create() Operator { return &PlusOperator{ OperatorBase: &OperatorBase{}, } } //4.2减法操作符: MinusOperator 和 该操作符的生产类:MinusOperatorFactory //MinusOperator Operator 的实际减法实现 type MinusOperator struct { *OperatorBase } func (o MinusOperator) Result() int { return o.a - o.b } type MinusOperatorFactory struct{} func (MinusOperatorFactory) Create() Operator { return &MinusOperator{ OperatorBase: &OperatorBase{}, } }
测试逻辑:
//定义计算函数,根据传进来的"特色工厂",首先生产出来"特色操作", 然后为其设置值,最后调用"特色"的计算函数 func compute(factory OperatorFactory, a, b int) int { op := factory.Create() op.SetA(a) op.SetB(b) return op.Result() } func TestFactoryMethod(){ var factory OperatorFactory //0.工厂接口句柄 factory = PlusOperatorFactory{} //1.句柄指向加法工厂 fmt.Println("1 + 2 =", compute(factory, 1, 2)) //结果:1 + 2 = 3 factory = MinusOperatorFactory{} //2.句柄指向减法工厂 fmt.Println("4 - 3 =", compute(factory, 4, 3)) //结果:4 - 3 = 1 }
3. 小小结
产品和生产产品的工厂全部从对应的顶级接口开始,
产品的实现类将接口方法的实现分成了两步 :
1)可复用的产品方法部分,抽象到一个类中,提前实现
2) 特有的产品方法部分,由各自的产品结构体自己根据的情况分别实现,即有一个产品才实现一个,属于延迟生成
不同的产品由自己的工厂的实现类,该实现类具体怎么实现工厂接口则依据自己负责哪个产品。
wxy:基类并没有实现顶级接口,而是由特色类补齐从而实现,这就是所谓的"延迟生成"? 但我的理解更应该站在产品类的角度,从而称之为"提前生成"
三:抽象工厂模式(Abstract Factory)
1. 核心思想
抽象工厂模式用于生成产品族的工厂,一个完整的产品不再是一个接口就能定义的,而是需要从多个维度进行划分
首先,其"组成结构"这个维度,可以包含多个部分,缺一不可,每个"部分"有不同种类的实现,但每一个部分都需要
然后,从"行为方式"这个维度,每个"产品部件"就在这"行为方式"维度下分别实现"组成结构"维度中的所有接口,机组合而得到。
具体的实现步骤为:
0,分析维度然后定义接口族,即每一个维度都包含多个"需求接口"
比如,一个订单,其组成结构成包括概要信息和详细信息(二者都要), 其保存则包含两种方式RDB或者XML(二选一)
1,定义维度1(在这里是订单的组成这个维度)的需求接口簇,几个部分则对应几个接口,分别为概要信息接口和详细信息接口;
2,从维度2(在这里是订单的保存方式)的角度,分别实现维度1的需求接口簇,即RDB方式下的2种需求接口和XML2方式下的两种需求;
3,定义1个工厂接口,接口要求实现的函数分别对应需求接口簇,即几个部分对应几个接口函数,分别是能够生产出订单的概要信息部分 和 详细信息部分
4,从维度2(在这里是订单的保存方式)的角度,分别实现工厂接口
2. 样例
//1.1.定义基接口1:称为main即主订单信息 和其两种实现类分别称为RDB和XML(代表存储订单信息的方式) type OrderMainDAO interface { SaveOrderMain() } //2.1.1 type RDBMainDAO struct{} func (*RDBMainDAO) SaveOrderMain() { fmt.Print("rdb main save ") } //2.1.2 type XMLMainDAO struct{} func (*XMLMainDAO) SaveOrderMain() { fmt.Print("xml main save ") } //1.2.定义基接口2:称为detail即详细订单信息, 和其两种实现类分别称为RDB和XML(代表存储订单信息的方式) type OrderDetailDAO interface { SaveOrderDetail() } //2.2.1 type RDBDetailDAO struct{} func (*RDBDetailDAO) SaveOrderDetail() { fmt.Print("rdb detail save ") } //2.2.2 type XMLDetailDAO struct{} func (*XMLDetailDAO) SaveOrderDetail() { fmt.Print("xml detail save") } //3, 定义工厂接口:即生产订单的工厂接口,包含两个实现类分别称为RDB和XML(代表存储订单信息的方式) type DAOFactory interface { CreateOrderMainDAO() OrderMainDAO CreateOrderDetailDAO() OrderDetailDAO } //4.1, 称为RDB方式订单信息生产工厂即可以生产如上两种接口类型的RDB实现类实例 type RDBDAOFactory struct{} func (*RDBDAOFactory) CreateOrderMainDAO() OrderMainDAO { return &RDBMainDAO{} } func (*RDBDAOFactory) CreateOrderDetailDAO() OrderDetailDAO { return &RDBDetailDAO{} } //4.2, 称为DAO方式订单信息生产工厂即可以生产如上两种接口类型的DAO实现类实例 type XMLDAOFactory struct{} func (*XMLDAOFactory) CreateOrderMainDAO() OrderMainDAO { return &XMLMainDAO{} } func (*XMLDAOFactory) CreateOrderDetailDAO() OrderDetailDAO { return &XMLDetailDAO{} }
测试逻辑:
//4. 如何从这两个维度(main or detail, RDB or XML)获取订单信息 //对于一个工厂来说:调用工厂的创建方法生产出来两种基类实例,并调用其业务函数 func getMainAndDetail(factory DAOFactory) { factory.CreateOrderMainDAO().SaveOrderMain() factory.CreateOrderDetailDAO().SaveOrderDetail() } //对于全局来说: 分别调用如下两个工厂生成逻辑 //1.一个工厂句柄,指向RDB类型工厂实例 func ExampleRdbFactory() { var factory DAOFactory factory = &RDBDAOFactory{} getMainAndDetail(factory) //结果:rdb main save } // rdb detail save //2.一个工厂句柄,指向XML类型工厂实例 func ExampleXmlFactory() { var factory DAOFactory factory = &XMLDAOFactory{} //结果:xml main save getMainAndDetail(factory) // xml detail save } func TestFactoryAbstract(){ ExampleRdbFactory() ExampleXmlFactory() }
3. 小小结
这类模式的特点是:
一方面产品有多个部分组成,另一方面每个部分要有多种形式,即一个产品需要从"组成"(与的关系) 和 "方式"(或的关系)两个维度来分析,
而工厂也不再是生产一个完整的产品,而是分别生产一个产品的各个组成部分,并且根据不同的方式有不同的工厂实例。
另外:
如果抽象工厂退化成生成的对象无关联则成为工厂函数模式。比如本例子中使用RDB和XML存储订单信息,抽象工厂分别能生成相关的主订单信息和订单详情信息。
如果业务逻辑中需要替换使用的时候只需要改动工厂函数相关的类就能替换使用不同的存储方式了。
wxy:为什么称为抽象工厂呢?
答: 因为工厂是一个接口,所以称为抽象,工厂的具体实现则根据"组成"和"方式"两个维度在实现