设计模式3-结构型模式

模式名称 介绍
代理模式 为其他对象提供一种代理以控制对这个对象的访问。
装饰器模式 动态的给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活。
适配器模式 将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
外观模式 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

代理模式

代理模式可以为其他对象提供一种代理,以控制这个对象的访问。

所谓代理,是指具有与被代理对象相同的接口类,客户端必须通过代理与被代理的目标进行交互,代理在这个交互的过程中,进行某些额外的处理。

比如说,一个普通的汉堡,直接吃则吃到的是普通的汉堡。但是如果利用代理,将这个普通的汉堡撒上辣椒粉,就可以得到一个香辣鸡腿堡。最后也吃到了一个香辣鸡腿堡。

image-20230117201043264

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)
}

代理模式的核心要点为代理和原对象实现相同接口,但扩大了原对象的职能。

装饰器模式

装饰器模式和代理模式很相似,都是给原有的类添加额外的功能。然而区别在于,装饰器模式的特点在于可以不断“迭代“。

如下图所示,一个手机可以贴膜变成带膜的手机,也可以装壳变成带壳的手机,带壳的手机也可以贴膜变成带壳膜的手机。

image-20230117204110642

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

如下图所示,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请求并发。

image-20230117214119644

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对象中的方法。

image-20230117215850340

此处不再举出代码案例。

这个系列的帖子