工厂方法模式(Factory Method)是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
工厂方法模式适合应用场景:
- 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。
- 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
- 如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。
问题
假设我们的业务需要一个支付渠道,我们开发了一个 Pay
方法,其可以用于支付。请看以下示例:
package main
import "fmt"
type Pay interface {
Pay() string
}
type PayReq struct {
OrderId string // 订单号
}
func (p *PayReq) Pay() string {
// todo
fmt.Println(p.OrderId)
return "支付成功"
}
func main() {
p := PayReq{OrderId: "12345678"}
fmt.Println(p.Pay())
}
如上,我们定义了接口 Pay
,并实现了其方法 Pay()
。
如果业务需求变更,需要我们提供多种支付方式,一种叫 APay
,一种叫 BPay
,这二种支付方式所需的参数不同,APay
只需要订单号 OrderId
,BPay
则需要订单号 OrderId
和 Uid
。此时如何修改?
很容易想到的是在原有的代码基础上修改,比如:
package main
import "fmt"
type Pay interface {
APay() string
BPay() string
}
type PayReq struct {
OrderId string // 订单号
Uid int64
}
func (p *PayReq) APay() string {
// todo
fmt.Println(p.OrderId)
return "APay支付成功"
}
func (p *PayReq) BPay() string {
// todo
fmt.Println(p.OrderId)
fmt.Println(p.Uid)
return "BPay支付成功"
}
func main() {
a := &PayReq{OrderId: "A12345678"}
fmt.Println(a.APay())
b := &PayReq{OrderId: "B12345678", Uid: 888}
fmt.Println(b.BPay())
}
我们为 Pay
接口实现了 APay()
和 BPay()
方法。虽然暂时实现了业务需求,但却使得结构体 PayReq
变得冗余了,APay()
并不需要 Uid
参数。如果之后再增加CPay
、DPay
、EPay
,可想而知,代码会变得越来越难以维护。随着后续业务迭代,将不得不编写出复杂的代码。
解决
让我们想象一个工厂类,这个工厂类需要生产电线和开关等器具,我们可以为工厂类提供一个生产方法,当电线机器调用生产方法时,就产出电线,当开关机器调用生产方法时,就产出开关。
套用到我们的支付业务来,就是我们不再为接口提供 APay
方法、BPay
方法,而只提供一个 Pay
方法,并将 A 支付方式和 B 支付方式的区别下放到子类。
package main
import "fmt"
type Pay interface {
Pay() string
}
type PayReq struct {
OrderId string
}
type APayReq struct {
PayReq
}
func (p *APayReq) Pay() string {
// todo
fmt.Println(p.OrderId)
return "APay支付成功"
}
type BPayReq struct {
PayReq
Uid int64
}
func (p *BPayReq) Pay() string {
// todo
fmt.Println(p.OrderId)
fmt.Println(p.Uid)
return "BPay支付成功"
}
func main() {
var pay Pay
pay = &APayReq{PayReq{"A12345678"}}
fmt.Println(pay.Pay())
pay = &BPayReq{PayReq: PayReq{"B12345678"}, Uid: 888}
fmt.Println(pay.Pay())
}
我们用 APay
和 BPay
两个结构体实现了 Pay()
方法,如果需要添加一种新的支付方式,只需要实现新类型的 Pay()
方法即可。
工厂方法的优点就在于避免了创建者和具体产品之间的紧密耦合,从而使得代码更容易维护。
参考文章: