上一节我们只讲了创建型设计模式中的工厂模式,更多的是在吐槽没有经过设计的代码实现会给未来造成怎样的麻烦。由于我们当前行业的人员流动性确实比较大,能够始终如一的对代码高要求、完善管理的公司确实不多了。只希望作为开发者的我们,始终对自己高要求,有着一颗追求技术的心。
把写代码当作在塑造艺术品的过程,写出来的代码自然也是一件艺术品。当他人再次阅读你的代码时,会惊叹多么美好,而不是一坨💩。
本节我们将继续讲解创建型设计模式中的抽象工厂模式。
由于Go没有继承的概念,在上一节中使用 interface 来代替基类的作用。上一节的代码示例已然有了抽象工厂的概念。
抽象工厂模式能够创建基于基类继承的或者实现某个接口的一系列对象,而无需知道具体实现的是哪个类。
回顾下上节的例子,使用New方法(工厂)生产出来的对象是Delivery接口,但是具体是 Plane 还是 Car 对于使用者是无感的。
// 运输方式
type Delivery interface {
DeliveryPackage() error
}
func New() *Delivery {
return ...
}
我们对这个例子进一步扩充,某个运送链路不仅仅只有运送包裹这一个方法,可能还有审核、获取运输时间等方法:
// 运输方式
type Delivery interface {
CheckPackage() error
DeliveryPackage() error
GetDeliveryTime() (int64, error)
}
而航空运输同货运肯定有着不同的实现方式,那么各自类代码如下:
// 汽车
type Car struct {
}
func (c Car) CheckPackage() error {
fmt.Println("货运货件检查")
return nil
}
func (c Car) DeliveryPackage() error {
fmt.Println("货运货件运输")
return nil
}
func (c Car) GetDeliveryTime() (int64, error) {
return 86400*3, nil
}
// 飞机
type Plane struct {
}
func (p Plane) CheckPackage() error {
fmt.Println("航空货件检查")
return nil
}
func (p Plane) DeliveryPackage() error {
fmt.Println("航空货件运输")
return nil
}
func (p Plane) GetDeliveryTime() (int64, error) {
return 86400, nil
}
工厂模式和抽象工厂模式的区别在于是继承还是接口实现,其原理都是一样的即用户无需关注生成的对象的具体类型,只需要使用该对象完成这些对象共有方法的调用。
由于Go没有继承的概念,所以工厂模式和抽象工厂模式在Go代码上的体现是一样的,本节的代码案例相较于上一节仅仅只是对具体的运输方式增加了方法,使其更接近生产实际中而已。
接下来我们看下创建型设计模式的另一种创建者模式。
我们还是从案例入手,假设需要代码模拟一家汽车生产商生产多个车型,每辆车都有汽车必备的:四个轮胎、座椅、发动机等,只是他们的品牌不同而已。每种车型也都有自己的选配装置如倒车影像、座椅加热、天窗等你会如何构造这个对象呢?
我们还是先来看反例代码,把这三个手机的所有属性都堆放在构造方法中,无论是哪一部手机的特性都会在构造代函数里,例如如下代码:
func NewPhone(tyre, seat, engine string, withBackupCamera, withSeatHeat, withTopWindow))
可能选配装置的装置不止这些,那构造方法的参数会比这个多得多,当参数越来越多了,再加上命名不规范又没有注释。无论开发者自己或者后续的维护人员根本就不太好理解每个参数了,当每次构造一个汽车对象都得研究很久构造方法的参数。
这个时候你就得使用创建者模式了,我们可以把共有的属性放在构造函数中,把特有的属性封装到每种类型的方法中作为构造步骤。
type Car interface {
DecorateBackupCamera() error
DecorateTopWindow() error
DecorateSeatHeat() error
}
type Car1 struct {
}
type Car2 struct {
}
func NewCar(tyre, seat, engine string) *Car {
// ...
}
func (c Car1) DecorateBackupCamera() error {
fmt.Println("car with BackupCamera")
return nil
}
func (c Car1) DecorateTopWindow() error {
return nil
}
func (c Car1) DecorateSeatHeat() error {
fmt.Println("car with DecorateSeatHeat")
}
func (c Car2) DecorateBackupCamera() error {
return nil
}
func (c Car2) DecorateTopWindow() error {
fmt.Println("car with DecorateTopWindow")
return nil
}
func (c Car2) DecorateSeatHeat() error {
return nil
}
上述案例中,Car1车型有配置倒车影像和座椅加热,Car2有配置天窗。由于Go语言 interface 的定义是实现了接口所有定义的方法也就实现了接口。所以在语言层面上Car1、Car2虽然都有没有配置的选装,但是都得实现对应的方法,在这里是直接返回nil了。
可能代码比较繁琐,如果选装项特别的多那么类的成员函数也就更多。让代码看上去比较多,但是很直观的通过函数体是否为空很容易查看某款车型是否具备某个选装。
创建者模式传达的思想是不要在构造函数中传入过多的参数,可以把熟悉的封装放入不同的成员函数中,使对象看起来像是一步步组装起来的而不是一锅粥的煮出来的。(就算是煮粥也有食材的先后顺序)
即使是某个普通的函数,也需要注意不要传入过多的参数。参数多,要么是参数冗余了,很多参数根本就没有用到;要么是这个函数中处理的逻辑太大了,也不方便代码的维护,可以多思考下是否可以拆成多步(多个函数)来处理,那么每个函数只需要处理自己逻辑的参数,自然参数也就减少了。
往期回顾:
go语言系列4 - Goroutine、channel的使用还有话说