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
...