设计模式3-结构型模式
模式名称 | 介绍 |
---|---|
代理模式 | 为其他对象提供一种代理以控制对这个对象的访问。 |
装饰器模式 | 动态的给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活。 |
适配器模式 | 将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 |
外观模式 | 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 |
代理模式
代理模式可以为其他对象提供一种代理,以控制这个对象的访问。
所谓代理,是指具有与被代理对象相同的接口类,客户端必须通过代理与被代理的目标进行交互,代理在这个交互的过程中,进行某些额外的处理。
比如说,一个普通的汉堡,直接吃则吃到的是普通的汉堡。但是如果利用代理,将这个普通的汉堡撒上辣椒粉,就可以得到一个香辣鸡腿堡。最后也吃到了一个香辣鸡腿堡。
package main
import "fmt"
type Goods struct {
Kind string //商品种类
Fact bool //商品真伪
}
// =========== 抽象层 ===========
//抽象的购物主题Subject
type Shopping interface {
Buy(goods *Goods) //某任务
}
// =========== 实现层 ===========
//具体的购物主题, 实现了shopping, 去韩国购物
type KoreaShopping struct {}
func (ks *KoreaShopping) Buy(goods *Goods) {
fmt.Println("去韩国进行了购物, 买了 ", goods.Kind)
}
//具体的购物主题, 实现了shopping, 去美国购物
type AmericanShopping struct {}
func (as *AmericanShopping) Buy(goods *Goods) {
fmt.Println("去美国进行了购物, 买了 ", goods.Kind)
}
//具体的购物主题, 实现了shopping, 去非洲购物
type AfrikaShopping struct {}
func (as *AfrikaShopping) Buy(goods *Goods) {
fmt.Println("去非洲进行了购物, 买了 ", goods.Kind)
}
//海外的代理
type OverseasProxy struct {
shopping Shopping //代理某个主题,这里是抽象类型
}
func (op *OverseasProxy) Buy(goods *Goods) {
// 1. 先验货
if (op.distinguish(goods) == true) {
//2. 进行购买
op.shopping.Buy(goods) //调用原被代理的具体主题任务
//3 海关安检
op.check(goods)
}
}
//创建一个代理,并且配置关联被代理的主题
func NewProxy(shopping Shopping) Shopping {
return &OverseasProxy{shopping}
}
//验货流程
func (op *OverseasProxy) distinguish(goods *Goods) bool {
fmt.Println("对[", goods.Kind,"]进行了辨别真伪.")
if (goods.Fact == false) {
fmt.Println("发现假货",goods.Kind,", 不应该购买。")
}
return goods.Fact
}
//安检流程
func (op *OverseasProxy) check(goods *Goods) {
fmt.Println("对[",goods.Kind,"] 进行了海关检查, 成功的带回祖国")
}
func main() {
g1 := Goods{
Kind: "韩国面膜",
Fact: true,
}
g2 := Goods{
Kind: "CET4证书",
Fact: false,
}
//如果不使用代理来完成从韩国购买任务
var shopping Shopping
shopping = new(KoreaShopping) //具体的购买主题
//1-先验货
if g1.Fact == true {
fmt.Println("对[", g1.Kind,"]进行了辨别真伪.")
//2-去韩国购买
shopping.Buy(&g1)
//3-海关安检
fmt.Println("对[",g1.Kind,"] 进行了海关检查, 成功的带回祖国")
}
fmt.Println("---------------以下是 使用 代理模式-------")
var overseasProxy Shopping
overseasProxy = NewProxy(shopping)
overseasProxy.Buy(&g1)
overseasProxy.Buy(&g2)
}
代理模式的核心要点为代理和原对象实现相同接口,但扩大了原对象的职能。
装饰器模式
装饰器模式和代理模式很相似,都是给原有的类添加额外的功能。然而区别在于,装饰器模式的特点在于可以不断“迭代“。
如下图所示,一个手机可以贴膜变成带膜的手机,也可以装壳变成带壳的手机,带壳的手机也可以贴膜变成带壳膜的手机。
Aceld的例子不是很直观,这里贴一个其他代码实现https://www.jianshu.com/p/f70f8b154be4
// 计算器
type Calculate interface {
Cal() int
}
//创建一个基准struct,后面的装饰器给这个基准struct加功能
type OriCalculate struct {
num int
}
func NewOriCalculate(num int)*OriCalculate{
return &OriCalculate{num:num}
}
func (o *OriCalculate)Cal() int{
return o.num
}
//创建基于基准struct的乘法
type MutCalculate struct{
Calculate
num int
}
func NewMutCalculate(C Calculate,num int) *MutCalculate {
return &MutCalculate{Calculate:C,num:num}
}
func (m *MutCalculate) Cal() int {
return m.num*m.Calculate.Cal()
}
//创建基于基准struct的加法
type AddCalculate struct {
Calculate
num int
}
func NewAddCalculate(C Calculate,num int) *AddCalculate {
return &AddCalculate{Calculate:C,num:num}
}
func (a *AddCalculate)Cal()int {
return a.num+a.Calculate.Cal()
}
// 使用
func ExampleDecorator() {
//o:=NewOriCalculate(1)
//定义的时候,接口实现的是指针接收,所以用指针初始化
o:=&OriCalculate{1}
m:=MutCalculate{o,2}
a:=AddCalculate{o,3}
log.Printf("Oricalculate is %d \n MutCalculate is %d\n AddCalculate is %d \n",o.Cal(),m.Cal(),a.Cal())
// Output: The output of
// this example.
}
通俗来讲,装饰器模式和代理模式最大的区别就在于,装饰器模式拥有一个迭代的特性,原类和装饰器都实现了同一个接口,那么每次这个“类”,被“装饰”后,他还是他自己(此处指拥有原本的方法),但是他成为了一个新的自己。在实际业务中,可以根据具体的业务需求来随意装饰。比代理模式更加灵活。
适配器模式
适配器模式较为简单,共有三个角色:适配目标,适配器,适配者
![image-20230117210840196](https://redis.fran6k.live:1181/i/2023/01/17/63c69ddb66b9e.png)
如下图所示,HttpClient利用成员HttpRequest.DoHttp()发送请求,除此外HttpClient包含了url,host,port,body等参数信息。
Http和RPC Adapter实现了HttpRequest接口。
默认情况下,当HttpClient调用DoHttp()方法时,将调用初始化时传入的Http对象中的Do()方法。当使用适配器时,将调用适配器RPC Adapter中的Do()方法。适配器将根据HttpClient中的url,host,port,body等参数信息来构造RPC请求并发。
package main
import "fmt"
type HttpRequest interface {
Do()
}
type HttpClient struct {
H HttpRequest
Url string
Host string
}
func (h *HttpClient) DoReq() {
h.H.Do()
}
func NewHttpClient(req HttpRequest) *HttpClient {
return &HttpClient{H: req}
}
// 实现HttpRequest就恶口
type Http struct{}
func (h *Http) Do() {
fmt.Println("发送http请求")
}
type RPCAdapter struct {
R *RPC
}
func (a *RPCAdapter) Do() {
// 做一些rpc适配
// 发送rpc请求s
a.R.DoRPC()
}
func NewRPCAdapter(r *RPC) HttpRequest {
return &RPCAdapter{R: r}
}
type RPC struct {
}
func (r *RPC) DoRPC() {
fmt.Println("发送rpc请求")
}
func main() {
// -----不使用适配器-----
http := NewHttpClient(&Http{})
http.DoReq()
// -----使用适配器-----
// 初始化一个rpc请求
rpc := &RPC{}
// 将rpc请求装入适配器
adapter := NewRPCAdapter(rpc)
// 初始化一个http请求
http = NewHttpClient(adapter)
// 发送http请求,实际上被转化成了rpc请求
http.DoReq()
}
适配器模式将目标类和适配者解耦,通过一个适配器类重现现有的适配者类。灵活性和拓展性较好,可以很方便的更换适配器,符合开闭原则。
外观模式
外观模式较为常见也较为简单。比如现在有ABCD四个对象的不同方法,当某个业务需要同时调用AB对象中的方法时,则需要分别调用两次。而如果此时使用外观模式,在AB对象外新加一层“外观”,当调用外观的方法时就自动调用了AB对象中的方法。
此处不再举出代码案例。