1. Go中的面向对象

面向对象三大特性:封装、继承、多态。 设计模式需遵循面向对象的设计原则,由于本文是通过go语言实现的,所以需要先了解go中的面向对象是怎么样的。

Golang中的面向对象是通过struct结构体实现的,类似于C++和Java中的Class类。其中struct类似C++的普通类类型,interface则对应抽象类类型。对象的成员变量可见性则是通过大小写字母开头来区分。

Golang中的继承是通过组合来实现的,下例基类是Base,子类是Foo;子类可以直接调用基类的公有方法,子类也可以定义自己的属性以及实现自己的方法。

type Base struct {
	Name string
}
func (base *Base) Bar() {
	fmt.Println(base.Name)
}

type Foo struct {
	Base
}
func (foo *Foo) Bar() {
	foo.Base.Bar()
}

func main() {
	f := Foo{Base{Name: "hello"}}
	f.Bar()
}

// hello

Golang中的多态是通过interface实现的,下例通过interface抽象出了不同品牌手机的价格,具体则是通过不同手机品牌的类来实现该手机品牌的价格。

type Phone interface {
	Price() float64
}

type Huawei struct {
	price float64
}
func (h *Huawei) Price() float64 {
	return h.price
}

type Xiaomi struct {
	price float64
}
func (x *Xiaomi) Price() float64 {
	return x.price
}
func NewXiaomiPrice(xiaomi *Xiaomi) Phone {
	return xiaomi
}

func main() {
	h := Huawei{price: 3000}
	fmt.Println(h.Price())
	x := Xiaomi{price: 2000}
	fmt.Println(x.Price())
    nx := NewXiaomiPrice(&Xiaomi{price: 2500})
	fmt.Println(nx.Price())
}

// 3000
// 2000
// 2500

2. 设计模式原则

2.1 单一职责原则

即一个类只对应一个职责,其职责是引起该类变化的原因,例如类T1只完成F1的功能,类T2只完成F2的功能,T1改变的时候不会影响F2,同理T2改变的时候不会影响F1,做到职责单一

type T1 struct {}
func (t *T1) F1() {}

type T2 struct {}
func (t *T2) F2() {}

func main() {
	t1 := new(T1)
	t1.F1()
	t2 := new(T2)
	t2.F2()
}

2.2 开闭原则

开闭原则即对程序的扩展是持开放态度的,对程序的修改是封闭态度的。目的是为了程序的扩展性好,易于维护,减少因为代码的多次修改而造成各种隐性bug,遵循高内聚,低耦合的原则。

在GO语言中需要用到interface字段对方法进行抽象,做到T1业务和Function和T2业务的Function互不影响,而且还可以扩展新业务而不用从之前的Function的修改

type Abstract interface {
	Function()
}

type T1 struct {}
func (t *T1) Function() {
	fmt.Println("t1业务")
}

type T2 struct {}
func (t *T2) Function() {
	fmt.Println("t2业务")
}

func Business(abstract Abstract) {
	abstract.Function()
}

func main() {
	Business(new(T1))	// t1业务
	Business(new(T2))   // t2业务
}

2.3 里氏代换原则

里氏代换原则规定子类不得重写父类的普通方法,只能重写父类的抽象方法;即子类可以扩展父类的功能,但是不能改变父类原有的功能

2.4 依赖倒转原则

依赖倒置原则的定义为:高层模块不应该依赖低层模块,而是高层和低层都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程。

例如下例就是耦合度极高的一个例子,固定死了peopleA play xiaomi games,peopleB play huawei games,如果我想peopleA play huawei games就还得写结构体类去实现,这样不利于扩展

type Xiaomi struct {}
func (x *Xiaomi) games() {
	fmt.Println("xiaomi games")
}

type Huawei struct {}
func (x *Huawei) games() {
	fmt.Println("huawei games")
}

type PeopleA struct {}
func (p *PeopleA) play(x *Xiaomi)  {
	fmt.Printf("peopleA play ")
	x.games()
}

type PeopleB struct {}
func (p *PeopleB) play(h *Huawei)  {
	fmt.Printf("peopleB play ")
	h.games()
}

func main() {
	x := &Xiaomi{}
	h := &Huawei{}
	a := &PeopleA{}	
	a.play(x) // peopleA play xiaomi games
	b := &PeopleB{} 
	b.play(h) // peopleB play huawei games
}

根据依赖倒转原则改进,人和手机可以分别抽象成两个接口(模块),模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生。下例中如果我想 peopleA play huawei games 可以直接在main函数中调用,而不用像上例还要手动实现类

type People interface {
	play(phone Phone)
}
type Phone interface {
	games()
}

type Xiaomi struct {}
func (x *Xiaomi) games() {
	fmt.Println("xiaomi games")
}

type Huawei struct {}
func (x *Huawei) games() {
	fmt.Println("huawei games")
}

type PeopleA struct {}
func (p *PeopleA) play(x Phone)  {
	fmt.Printf("peopleA play ")
	x.games()
}

type PeopleB struct {}
func (p *PeopleB) play(h Phone)  {
	fmt.Printf("peopleB play ")
	h.games()
}

func main() {
	x := &Xiaomi{}
	h := &Huawei{}
	pa := &PeopleA{} 
	pb := &PeopleB{} 
	pa.play(x) // peopleA play xiaomi games
	pb.play(h) // peopleB play huawei games
}

2.5 接口隔离原则

客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。简单地说,就是使用多个专门的接口比使用单个接口要好很多。

2.6 合成复用原则

如果使用继承,会导致父类的任何变换都可能影响到子类的行为,所以优先使用组合的方式代替继承的方式。

2.7 迪米特法则

又叫最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立;降低类之间的耦合度,提高模块的相对独立性

3. 设计模式

3.1 创建型模式

3.1.1 工厂方法模式

抽象工厂接口+具体工厂+抽象产品接口+具体产品

优点:一个调用者想创建一个对象,只要知道其名称就可以了,易扩展

缺点:每次增加具体产品时需要新增实现,增加了代码量和复杂度

type Phone interface {
	Show()
}

type AbstractFactory interface {
	ProducePhone() Phone
}

// 手机模块
type Xiaomi struct {}
func (x *Xiaomi) Show()  {
	fmt.Println("xiaomi")
}

type Huawei struct {}
func (h *Huawei) Show()  {
	fmt.Println("huawei")
}

// 手机工厂模块
type XiaomiFactory struct {}
func (x *XiaomiFactory) ProducePhone() Phone {
	return new(Xiaomi)
}

type HuaweiFactory struct {}
func (h *HuaweiFactory) ProducePhone() Phone {
	return new(Huawei)
}

func main() {
	f1 := new(XiaomiFactory)
	f1.ProducePhone().Show() // 生产小米手机

	f2 := new(HuaweiFactory)
	f2.ProducePhone().Show() // 生产华为手机
}

3.1.2 抽象工厂模式

在工厂方法模式中,每一个工厂只生产一类产品,导致大量工厂类的存在。

因此抽象工厂模式在工厂的维度又抽象了一层,使得增加新的产品族时(如增加一个厂商)方便,但是当新增产品等级结构(如Intel厂商下新增其他配件)时会修改原来的抽象层代码,违背了开闭原则;

因此抽象工厂模式适用于产品族较多,且每一个产品族内的产品等级结构稳定的情况。

用抽象工厂模式实现下面例子:

设计一个电脑主板架构,电脑包括(显卡,内存,CPU)3个固定的插口,显卡具有显示功能(display,功能实现只要打印出意义即可),内存具有存储功能(storage),cpu具有计算功能(calculate)。现有Intel厂商,nvidia厂商,Kingston厂商,均会生产以上三种硬件。要求组装两台电脑:

  • 1台(Intel的CPU,Intel的显卡,Intel的内存)
  • 1台(Intel的CPU, nvidia的显卡,Kingston的内存)
// 抽象层
type AbstractGPU interface {
	Display()
}
type AbstractCPU interface {
	Calculate()
}
type AbstractMemory interface {
	Storage()
}

// 抽象工厂
type AbstractFactory interface {
	CreateGPU() AbstractGPU
	CreateCPU() AbstractCPU
	CreateMemory() AbstractMemory
}
// Intel 厂商
type IntelGPU struct{}
type IntelCPU struct{}
type IntelMemory struct{}
type IntelFactory struct{}

func (i *IntelGPU) Display() {
	fmt.Printf("Intel GPU display,")
}
func (i *IntelCPU) Calculate() {
	fmt.Printf("Intel CPU calculate,")
}
func (i *IntelMemory) Storage() {
	fmt.Printf("Intel memory storage,")
}
func (i *IntelFactory) CreateIntelGPU() AbstractGPU {
	return new(IntelGPU)
}
func (i *IntelFactory) CreateIntelCPU() AbstractCPU {
	return new(IntelCPU)
}
func (i *IntelFactory) CreateIntelMemory() AbstractMemory {
	return new(IntelMemory)
}
// Nvidia 厂商
type NvidiaGPU struct{}
type NvidiaCPU struct{}
type NvidiaMemory struct{}
type NvidiaFactory struct{}

func (n *NvidiaGPU) Display() {
	fmt.Printf("Nvidia GPU display,")
}
func (n *NvidiaCPU) Calculate() {
	fmt.Printf("Nvidia CPU calculate,")
}
func (n *NvidiaMemory) Storage() {
	fmt.Printf("Nvidia memory storage,")
}
func (n *NvidiaFactory) CreateNvidiaGPU() AbstractGPU {
	return new(NvidiaGPU)
}
func (n *NvidiaFactory) CreateNvidiaCPU() AbstractCPU {
	return new(NvidiaCPU)
}
func (n *NvidiaFactory) CreateNvidiaMemory() AbstractMemory {
	return new(NvidiaMemory)
}
// Kingston 厂商
type KingstonGPU struct{}
type KingstonCPU struct{}
type KingstonMemory struct{}
type KingstonFactory struct{}

func (k *KingstonGPU) Display() {
	fmt.Printf("Kingston GPU display,")
}
func (k *KingstonCPU) Calculate() {
	fmt.Printf("Kingston CPU calculate,")
}
func (k *KingstonMemory) Storage() {
	fmt.Printf("Kingston memory storage,")
}
func (k *KingstonFactory) CreateKingstonGPU() AbstractGPU {
	return new(KingstonGPU)
}
func (k *KingstonFactory) CreateKingstonCPU() AbstractCPU {
	return new(KingstonCPU)
}
func (k *KingstonFactory) CreateKingstonMemory() AbstractMemory {
	return new(KingstonMemory)
}
func main() {
	// Intel的CPU,Intel的显卡,Intel的内存
	intelFac := new(IntelFactory)
	intelFac.CreateIntelCPU().Calculate()
	intelFac.CreateIntelGPU().Display()
	intelFac.CreateIntelMemory().Storage()
	fmt.Println()

	// Intel的CPU, nvidia的显卡,Kingston的内存
	nvidiaFac := new(NvidiaFactory)
	kingstonFac := new(KingstonFactory)
	intelFac.CreateIntelCPU().Calculate()
	nvidiaFac.CreateNvidiaGPU().Display()
	kingstonFac.CreateKingstonMemory().Storage()
}

3.1.3 单例模式

定义:保证一个类、只有一个实例存在,同时提供能对该实例访问的全局访问方法

var once sync.Once
var singleInstance *single

type single struct{}	// 首字母小写,不对外暴露

func GetSingleInstance() *single {
	if singleInstance == nil {
		once.Do(func() { // 只会执行一次,即使在并发的情况下
			fmt.Println("Create single instance.")
			singleInstance = &single{}
		})
	} else {
        // 之后使用已创建的实例
		fmt.Println("Get single instance.")
	}
	return singleInstance
}

func main() {
	for i := 0; i < 10; i++ {
		go GetSingleInstance()
	}
	time.Sleep(time.Second * 60)
}

// Create single instance.
// Get single instance.
// Get single instance.

3.1.4 原型模式

3.1.5 生成器模式

3.2 结构型模式

3.2.1 代理模式

代理模式分为静态代理和动态代理,由于动态代理较为复杂,这里不给出具体案例,后续有时间再更新。

静态代理一般写法是提供一个代理类,该代理类能将代理对象与真实被调用目标对象分离,可以起到保护目标对象的作用,因此,代理类需要实现和目标类相同的接口,并且代理类需要将目标类中的所有方法都要重新实现,在新实现的方法中可以添加一些其他处理,起到增强目标对象的效果。

比较合适的例子就是买房时通过中介买房的过程,下面是实现案例:

// 买房抽象接口
type BuyHouse interface {
	Look()
	Talk()
	Buy()
}

// 具体买房者
type Buyer struct {
	Name string
}
func (b *Buyer) Look() {
	fmt.Println(b.Name, " 看房子")
}
func (b *Buyer) Talk() {
	fmt.Println(b.Name, " 和房东交谈")
}
func (b *Buyer) Buy() {
	fmt.Println(b.Name, " 决定买房子")
}

// 代理买房者(中介)
type ProxyBuyer struct {
	Buyer *Buyer
}
func (p *ProxyBuyer) Look() {
	fmt.Printf("看房前中介联系了 ")
	p.Buyer.Look()
}
func (p *ProxyBuyer) Talk() {
	fmt.Printf("中介建议 ")
	p.Buyer.Talk()
}
func (p *ProxyBuyer) Buy() {
	fmt.Printf("中介得知 ")
	p.Buyer.Buy()
}
func NewProxyBuyer(name string) *ProxyBuyer {
	return &ProxyBuyer{
		Buyer: &Buyer{
			Name: name,
		},
	}
}

func main() {
	proxy := NewProxyBuyer("小明")
	proxy.Look()
	proxy.Talk()
	proxy.Buy()
}

// 看房前中介联系了 小明  看房子
// 中介建议 小明  和房东交谈
// 中介得知 小明  决定买房子

3.2.2 装饰器模式

指在不改变原有对象结构的基础情况下,动态地给该对象增加一些额外功能的职责。装饰器模式解决的主要问题就是由继承带来的子类膨胀的问题,装饰器模式相比生成子类更加灵活,由于目标对象和装饰器遵循同一接口, 因此可用装饰来对对象进行无限次的封装,结果对象将获得所有封装器叠加而来的行为。

// 抽象产品接口
type product interface {
	getPrice() float64
}

// 具体产品
type concreteProduct struct {
	price float64
}
func (c *concreteProduct) getPrice() float64 {
	c.price = 10.0
	return c.price
}
// 添加具体装饰类,涨价
type productAddDecorator struct {
	product product
}
func (p *productAddDecorator) getPrice() float64 {
	return p.product.getPrice() + 3.0
}

// 添加具体装饰类,降价
type productDelDecorator struct {
	product product
}
func (p *productDelDecorator) getPrice() float64 {
	return p.product.getPrice() - 2.0
}

func main() {
	p := new(concreteProduct)
	fmt.Println("原价:", p.getPrice())

	add := &productAddDecorator{product: p}
	fmt.Println("涨价后:", add.getPrice())

	del := &productDelDecorator{product: add}
	fmt.Println("降价后:", del.getPrice())
}

// 原价: 10
// 涨价后: 13
// 降价后: 11

3.2.3 适配器模式

适配器模式将一个类的接口适配成用户所期待的。一个适配器允许因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。

原始角色(adaptee)->适配器(adapter)->目标角色(target)

例如我有一个原始角色:带usb接口的windows笔记本,但是我想要他变成能兼容lighting接口的windows笔记本,此时可以使用适配器模式。

// 客户端
type Client struct{}
func (c *Client) InsertLightningConnectorIntoComputer(com Computer) {
	fmt.Println("客户使用lightning接口")
	com.InsertIntoLightningPort()
}

// Computer
type Computer interface {
	InsertIntoLightningPort()
}

type Mac struct {}
func (m *Mac) InsertIntoLightningPort() {
	fmt.Println("lightning接口连接到mac")
}

type Windows struct {}
func (w *Windows) InsertIntoUsbPort() {
	fmt.Println("usb接口连接到windows")
}

type WindowsAdapter struct {
	windowMachine *Windows
}
func (w *WindowsAdapter) InsertIntoLightningPort() {
	fmt.Println("适配器把lightning接口适配成use接口")
	w.windowMachine.InsertIntoUsbPort()
}

func main() {
    client := &Client{}
	mac := &Mac{}
	client.InsertLightningConnectorIntoComputer(mac)
    
	windowsMachine := &Windows{}
	windowsMachineAdapter := &WindowsAdapter{
		windowMachine: windowsMachine,
	}
	client.InsertLightningConnectorIntoComputer(windowsMachineAdapter)
}

// 客户使用lightning接口
// lightning接口连接到mac
// 客户使用lightning接口
// 适配器把lightning接口适配成use接口
// usb接口连接到windows

3.2.4 外观模式

外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。

客户端->外观角色接口(Facade)->各个子系统(SubSystem)

例如:去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便,可满足不同顾客需求。

// 挂号
type registration struct{}
func (r *registration) Registration() {
	fmt.Println("挂号")
}

// 门诊
type outpatientService struct{}
func (o *outpatientService) OutpatientService() {
	fmt.Println("门诊")
}

// 付费
type pricing struct{}
func (p *pricing) Pricing() {
	fmt.Println("付费")
}

// 取药
type medicationCollection struct{}
func (m *medicationCollection) MedicationCollection() {
	fmt.Println("取药")
}

// 接待人员
type receptionist struct {
	registration         registration
	outpatientService    outpatientService
	pricing              pricing
	medicationCollection medicationCollection
}

// 想去看病的流程
func (r *receptionist) FirstVisit() {
	r.registration.Registration()
	r.outpatientService.OutpatientService()
	r.pricing.Pricing()
	r.medicationCollection.MedicationCollection()
}

// 看完了只想想拿药的流程
func (r *receptionist) MedicationCollection() {
	r.medicationCollection.MedicationCollection()
}
func main() {
    // 情况一
	receptionist := receptionist{}
	fmt.Println("1. 想去看病的流程: ")
	receptionist.FirstVisit() // 挂号 门诊 付费 取药

	// 情况二
	fmt.Println("2. 看完了只想想拿药的流程: ")
	receptionist.MedicationCollection() // 只取药
}

// 1. 想去看病的流程: 
// 挂号
// 门诊
// 付费
// 取药
// 2. 看完了只想想拿药的流程: 
// 取药

3.2.5 桥接模式

3.2.6 组合模式

3.2.7 享元模式

3.3 行为模式

3.3.1 模板方法模式

3.3.2 命令模式

3.3.3 策略模式

3.3.4 观察者模式

3.3.5 责任链模式

3.3.6 迭代器模式

3.3.7 中介者模式

3.3.8 备忘录模式

3.3.9 状态模式

3.3.10 访问者模式

4. 参考链接

[1]. https://www.bilibili.com/video/BV1Eg411m7rV/?spm_id_from=333.999.0.0

[2]. https://refactoringguru.cn/