Go 项目开发常用设计模式

news2024/11/18 17:29:21

设计模式就某些编码场景下的最佳实践,用来解决常见的软件设计问题。Go 语言不是面向对象语言,但是可以使用结构体、接口等类型实现面向对象语言的特性,想要弄懂设计模式,要熟练的使用 Go 语言接口类型 和结构体类型

设计模式总体上分为创建型、结构型和行为型 3 类、共 25 种经典设计方案,在 Go 项目开发中常见的有 6 种

下面详细说

创建型

创建型设计模式提供了一种 在创建对象的同时隐藏创建逻辑 的方式,而不是使用 new 运算符直接实例化对象

这种类型的设计模式有单例模式和工厂模式,工厂模式包括简单工厂模式、抽象工厂模式和工厂方法模式三种,这种设计模式在 Go 项目开发种比较常用

单例模式

单例模式是最简单的一个模式。在 Go 中,单例模式指的就是全局只有一个实例,而且只被初始化一次,比较适合 全局共享一个示例,并且只需要被初始化一次 的场景,比如数据库实例、全局配置、全局任务池等

单例模式又分为饿汉方式和懒汉方式,饿汉方式是全局的单例实例在包被加载时创建,懒汉方式是全局的单例实例在第一次被使用时创建

饿汉方式的单例模式:

type singleton struct{}
​
var ins *singleton = &singleton{}
​
func GetInsOr() *singleton {
    return ins
}

在包被导入时,实例会直接初始化,如果初始化耗时,会导致程序加载时间变长

懒汉方式是开源项目中使用最多的,它的缺点是非并发安全,在实际使用时需要加锁,一个简单的实现:

type singleton struct{}
​
var ins *singleton
​
func GetInsOr() *singleton {
    if ins == nil {
        ins = &singleton{}
    }
    
    return ins
}

可以看到,在调用 GetInsOr() 函数时,如果 ins 为 nil,就会创建一个 ins 实例,如果不加锁,就会有多个实例创建

可以对实例加锁,保证并发安全:

import "sync"
​
type singleton struct{}
​
var ins *singleton
var mu sync.Mutex
​
func GetInsOr() *singleton {
    if ins == nil {
        mu.Lock()
        if ins == nil {
            ins = &singleton{}
        }
        mu.Unlock()
    }
    
    return ins
}

要注意加锁后需要再判断是否已经创建好实例。这样就保证了并发安全

除了饿汉方式和懒汉方式,在 Go 开发中还有一种更优雅的实现方式,比较推荐使用:

import "sync"
​
type singleton struct{}
​
var ins *singleton
var once sync.Once
​
func GetInsOr() *singleton {
    once.Do(func() {
        ins = &singleton{}
    })
    return ins
}

sync.Once 是一个结构体,它提供的 Do 方法可以确保 ins 实例全局只被创建一次,还可以确保在并发场景下,只有一个线程能执行这个函数,Do 方法的参数只能是一个没有参数和返回值的匿名函数,用于做一些初始化操作

工厂模式

工厂模式是面向对象编程中的常用模式,在 Go 中,可以把结构体理解为类,比如:

type Person struct {
    Name string
    Age int
}
​
func (p Person) Greet() {
    fmt.Println("111")
}

Person 结构体实现了 Greet 方法,有了 Person 结构体,就可以通过简单工厂模式、抽象工厂模式、工厂方法模式这三种方式来创建一个 Person 实例

简单工厂模式是最常用、最简单的,它就是接收一些参数,然后返回 Person 实例:

type Person struct {
    Name string
    Age int
}
​
func (p Person) Greet() {
    fmt.Println("111")
}
​
func NewPerson(name string, age int) *Person {
    return &Person {
        Name: name,
        Age: age,
    }
}

p := &Person{} 这种创建方式相比,简单工厂模式可以确保创建的实例具有需要的参数,进而保证实例的方法可以按预期执行,比如通过 NewPerson 方法创建的 Person 实例,可以确保实例的 name 和 age 属性被设置

抽象工厂模式和简单工厂模式的唯一区别,就是返回的是接口而不是结构体

通过返回接口,可以在 不公开内部实现的情况下,让调用者使用提供好的各种功能,比如:

type Person interface {
    Greet()
}
​
type person struct {
    name string
    age int
}
​
func (p person) Greet() {
    fmt.Println("111")
}
​
func NewPerson(name string, age int) Person {
    return person {
        name: name,
        age: age,
    }
}

注意接口名是开头大写的 Person,而结构体是开头小写的 person,在 Go 中,开头小写的结构体是不能被导出的,在上面的例子中,只能通过 NewPerson 函数去生成接口类型的实例,这样就隐藏了 person 结构体的内部实现细节

通过返回接口类型,还可以实现多个工厂函数,来实现返回不同的接口实现:

type Doer interface {
    Do(req *http.Request) (*http.Response, error)
}
​
func NewHTTPClient() Doer {
    return &http.Client{}
}
​
// mock 的 HTTP 连接 用于模拟外部连接
type mockHTTPClient struct{}
​
func (*mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
    // 假设 httptest.NewRecorder 是实现好的方法
    // 用于返回一个新的 request 实例
    res := httptest.NewRecorder()
    
    return res.Result, nil
}
​
func NewMockHTTPClient() Doer {
    return &mockHTTPClient{}
}

NewHTTPClientNewMockHTTPClient 都返回了同一个接口类型 Doer,这使得两者可以互相使用,如果想测试一段调用了 Doer 接口的 Do 方法的代码时,就可以使用 Mock 出来的 HTTP 客户端,避免调用外部接口带来的失败,只专注于测试想测试的代码片段

比如现在想测试下面这段代码:

func QueryUser(doer Doer) error {
    req, err := http.NewRequest("Get", "http://iam.api.marmotedu.com:8080/v1/secrets", nil)
    if err != nil {
        return err
    }
    
    _, err := doer.Do(req)
    if err != nil {
        return err
    }
    
    // 处理一些其他逻辑
    // ...
    
    return nil
}

给这段代码编写测试用例为:

func TestQueryUser(t *testing.T) {
    doer := NewMockHTTPClient()
    if err := QueryUser(doer); err != nil {
        t.Errof(QueryUser failed, err: %v", err)
    }
}

这个测试用例忽略了请求外部的 http://iam.api.marmotedu.com:8080/v1/secrets 带来的错误,只专注于核心业务逻辑

另外,在使用简单工厂模式和抽象工厂模式返回实例对象时,都可以返回指针,比如:

简单工厂模式:

return &Person {
    Name: name,
    Age: age
}

抽象工厂模式:

return &person {
    Name: name,
    Age: age
}

但是在实际开发中,推荐使用非指针的实例,因为使用工厂模式是想通过创建实例,来调用其提供的方法,而不是对实例做更改,如果要对实例进行更改,可以给实例实现 SetXXX 方法,返回非指针的实例,可以避免属性被意外修改

在简单工厂模式中,依赖于唯一的工厂对象,如果需要创建一个实例,就要向工厂中传入一个参数,如果工厂函数要根据传入的参数值返回不同类型的实例,如果要创建一种新的实例,就需要在工厂中修改函数,这会导致耦合度过高,这时候就可以使用 工厂方法模式

在工厂方法模式中,依赖工厂函数,通过工厂函数来创建多种工厂,把实例创建从 由一个对象负责所有具体实例的实例化,变成一群子实例负责对具体实例的实例化,从而将过程解耦

比如:

type Person struct {
    name string
    age int
}
​
func NewPersonFactory(age int) func(name string) Person {
    return func(name string) Person {
        return Person {
            name: name,
            age: age,
        }
    }
}

NewPersonFactory函数返回了一个闭包函数,使用时可以创建具有默认年龄的工厂:

newBaby := NewPersonFactory(1)
baby := newBaby("john")
​
newTeenager := NewPersonFactory(16)
teen := newTeenager("jill")

结构型模式

结构型模式关注 类和对象的组合,这一类型中有策略模式和模板模式

策略模式

策略模式定义了一组算法,将每个算法封装起来,并且使它们之间可以互换

在项目开发中,经常要根据不同的场景,采取不同的措施,也就是不同的策略。比如要对 a、b 这两个整数进行运算,根据条件的不同,需要执行不同的计算方式,就可以把所有操作封装在同一个函数中,通过 if ... else ... 来调用不同的计算方式,这种方式称之为硬编码

在实际应用中,随着功能和体验的不断增长,经常需要增加/修改策略,这样就需要不断修改已有的代码,这不仅会让这个函数越来越难维护,还可能因为修改带来一些 bug,为了解耦,就需要使用策略模式,定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法

比如:

// 策略类
type IStrategy interface {
    do(int, int) int
}
​
// 策略实现:加
type add struct{}
​
func (*add) do(a, b int) int {
    return a + b
}
​
// 策略实现:减
type reduce struct{}
​
func (*reduce) do(a, b int) int {
    return a - b
}
​
// 具体策略的执行者
type Operator struct {
    strategy IStrategy
}
​
// 设置策略
func (op *Operator) setStrategy(strategy IStrategy) {
    op.strategy = strategy
}
​
// 调用策略中的方法
func (op *Operator) calculate(a, b int) int {
    return op.strategy.do(a, b)
}

在这段代码中,定义了策略接口 ISstrategy,还定义了 addreduce 这两种策略,最后定义了一个策略执行者,可以设置不同的策略并执行,比如:

func TestStrategy(t *testing.T) {
    op := Operator{}
    
    // 设置策略为 加
    op.setStrategy(&add{})
    result := op.calculate(1, 2)
    fmt.Println("add:", result)
    
    op.setStrategy(&reduce{})
    result = operator.calculate(2, 1)
    fmt.Println("reduce:", result)
}

这样就可以随意更换策略,而不影响 Operator 的所有实现

模板模式

模板模式定义一个操作中算法的骨架,将一些步骤延迟到子类中,这种方法可以让子类在不改变一个算法结构的情况下,能重新定义该算法的某些特定步骤

实现上,模板模式将一个类中的公用方法放在抽象类中实现,不能公共使用的方法作为抽象方法,强制子类去实现。这样就做到了将一个类作为一个模板,让开发者去填充需要填充的地方

比如:

type Cooker interface {
    fire()
    cooke()
    outfire()
}
​
// 类似于一个抽象类
type CookMenu struct{}
​
func (CookMenu) fire() {
    fmt.Println("开火")
}
​
// 做菜,交给具体的子类实现
func (CookMenu) cooke() {
}
​
func (CookMenu) outfire() {
    fmt.Println("关火")
}
​
// 封装具体步骤
func doCook(cook Cooker) {
    cook.fire()
    cook.cooke()
    cook.outfire()
}
​
type XiHongShi struct {
    CookMenu
}
func (*XiHongShi) cooke() {
    fmt.Println("做西红柿")
}
​
type ChaoJiDan struct {
    CookMenu
}
func (ChaoJiDan) cooke() {
    fmt.Println("做炒鸡蛋")
}

在上面这段代码中,把通用的开火和关火交给了抽象父类实现,子类通过结构体嵌套的方式继承了通用方法,再自己实现对应的 cooke() 方法。对应的测试用例为:

func TestTemplate(t *testing.T) {
    // 做西红柿
    xihongshi := &XiHongShi{}
    doCook(xihongshi)
    
    // 做炒鸡蛋
    chaojidan := &ChaoJiDan{}
    doCook(chaojidan)
}

行为型模式

行为型模式关注 对象之间的通信,这一类的设计模式中,有代理模式和选项模式

代理模式

代理模式可以为另一个对象提供一个替身或占位符,用来控制对这个对象的访问

比如:

type Seller interface {
    sell(name string)
}
​
// 火车站
type Station struct {
    stock int // 库存
}
​
func (station *Station) sell(name string) {
    if station.stock > 0 {
        station.stock--
        fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, station.stock)
    } else {
        fmt.Println("票已售空")
    }
}
​
// 火车代理点
type StationProxy struct {
    station *Station // 持有一个火车站对象
}
​
func (proxy *StationProxy) sell(name string) {
    // 增加一些其他逻辑,比如权限校验
    if proxy.station.stock > 0 {
        proxy.station.stock--
        fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, proxy.station.stock)
    } else {
        fmt.Println("票已售空")
    }
}

在这段代码中,StationProxy代理了 Station,代理类中持有被代理类对象,且和被代理类实现了统一接口。代理类主要是为了增加一种控制机制,在 StationProxy 实现的 sell 方法中,可以增加一些其他的逻辑

选项模式

选项模式是 Go 项目开发中经常用到的模式,比如 grpc/grpc-go的 NewServer 函数,uber-go/zap 包的 New 函数,都用到了选项模式

使用选项模式可以创建一个带有默认值的 struct 变量,并选择性的修改其中一些参数的值

Go 语言中不支持给参数设置默认值,为了既能够创建带默认值的实例,又能创建自定义参数的实例,不使用选项模式,一般有两种写法。

第一种方法是,分别创建两个用来创建实例的函数,一个可以创建带默认值的实例,一个可以定制化参数创建实例

const (
    defaultTimeout = 10
    defaultCaching = false
)
​
type Connection struct {
    addr    string
    cache   bool
    timeout time.Duration
}
​
// 创建一个连接对象 需要路径参数
func NewConnect(addr string) (*Connection, error) {
    return &Connection{
        addr:    addr,
        cache:   defaultCaching,
        timeout: defaultTimeout,
    }, nil
}
​
// 创建一个连接对象,需要路径参数和一些配置参数
func NewConnectWithOptions(addr string, cache bool, timeout time.Duration) (*Connection, error) {
    return &Connection{
        addr:    addr,
        cache:   cache,
        timeout: timeout,
    }, nil
}

这种写法创建一个 Connection 实例,却要实现两个不同的函数,很麻烦,如果 Connection 结构体又增加了新属性,那么也要再编写一个带有这个新属性的构造方法

另一种写法是创建一个带默认值的选项,并用该选项创建实例:

const (
    defaultTimeout = 10
    defaultCaching = false
)
​
type Connection struct {
    addr    string
    cache   bool
    timeout time.Duration
}
​
type ConnectionOptions struct {
    Caching bool
    Timeout time.Duration
}
​
// 默认选项
func NewDefaultOptions() *ConnectionOptions {
    return &ConnectionOptions{
        Caching: defaultCaching,
        Timeout: defaultTimeout,
    }
}
​
// 传入选项结构体和地址
func NewConnect(addr string, opts *ConnectionOptions) (*Connection, error) {
    return &Connection{
        addr:    addr,
        cache:   opts.Caching,
        timeout: opts.Timeout,
    }, nil
}

使用这种方式,虽然只需要一个函数来创建实例,但是调用 NewConnect 函数创建实例时,每次都要先创建一个 ConnectionOptions 结构体,操作起来比较麻烦

上面两种都有各自的缺点,使用选项模式可以更优雅的解决:

const (
    defaultTimeout = 10
    defaultCaching = false
)
​
type Connection struct {
    addr    string
    cache   bool
    timeout time.Duration
}
​
// 配置选项
type options struct {
    timeout time.Duration
    caching bool
}
​
// 接口类型 要实现 apply 方法
type Option interface {
    apply(*options)
}
​
// 函数类型起别名 类型是参数为 *options 返回为空的函数
type optionFunc func(*options)
​
func (f optionFunc) apply(o *options) {
    f(o)
}
​
func WithTimeout(t time.Duration) Option {
    // 把参数为 *options 返回为空的函数类型转换为 optionFunc 类型
    return optionFunc(func(o *options) {
        o.timeout = t
    })
}
​
func WithCaching(cache bool) Option {
    return optionFunc(func(o *options) {
        o.caching = cache
    })
}
​
// 创建一个连接对象 ... 表示可以有多个 Option 接口类型的参数
func NewConnect(addr string, ops ...Option) (*Connection, error) {
    options := options {
        timeout: defaultTimeout,
        caching: defaultCaching
    }
    
    for _, o := range ops {
        o.apply(&options)
    }
    
    return &Connection{
        addr:    addr,
        cache:   options.caching,
        timeout: options.timeout,
    }, nil
}

在这段代码中,首先定义了 options 结构体,它带有 timeout、caching 两个属性。接下来通过 NewConnect 创建连接,NewConnect 函数首先创建了一个带有默认值的 options 结构体,然后通过传入的 Option 参数,去修改 options 结构体,最后完成创建

在调用时,传入 WithXXX 格式的函数即可完成配置,因为函数返回值是 optionFunc 类型,而 optionFunc 类型又实现了 Option 接口,这就实现了动态设置 options 结构体变量的属性

选项模式有很多有点,例如:支持传递多个参数,在参数发生变化时保持兼容性,支持任意顺序传递参数,支持默认值,方便扩展等等

但是,为了实现选项模式,要增加很多代码,在开发中,要根据实际场景选择是否使用选项模式

选项模式适用的场景有:

  • 结构体参数很多,创建结构体时期望创建一个携带默认值的结构体变量,并选择性修改其中一些参数值

  • 结构体参数经常变动,变动时又不想修改创建实例的函数,比如:结构体新增一个 retry 参数,但是又不想在 NewConnect 入参列表中添加 retry int 这样的参数声明

如果结构体参数比较少,要慎重考虑要不要采用选项模式

总结

设计模式,是业界沉淀下来的针对特定场景的最佳解决方案,Go 项目常见的有 6 种设计模式,每种设计模式解决某一类场景

汇总成一张表:

参考:

设计模式 | 菜鸟教程 (runoob.com)

Go 语言项目开发实战 -11.Go常用设计模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2173797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于Chrome浏览器F12调试,显示未连接到互联网的问题

情况说明 最近笔者更新下电脑的Chrome浏览器,在调试前端代码的时候,遇到下面一个情况: 发现打开调试面板后,页面上显示未连接到互联网,但实际电脑网络是没有问题的,关闭调试面板后,网页又能正…

Python 爬虫 根据ID获得UP视频信息

思路: 用selenium库对网页进行获取,然后用bs4进行分析,拿到bv号,标题,封面,时长,播放量,发布时间 先启动webdriver.,进入网页之后,先等几秒,等加…

chatglm本地服务器大模型量化cpu INT4 INT8 half float运行、多卡多GPU运行改这一条指令就行啦!

一、ChatGLM3的几种推演方式 ChatGLM3常规方案的GPU推演中half和float是两种最常用的格式,half格式占13GB显存,float格式占40GB显存。此外还提供了几种GPU量化格式的推演:INT4和INT8量化。 CPU版本的ChatGLM3推演: model Auto…

Java 常用的一些Collection的实现类

Java 常用的一些Collection的实现类 Collection 1.集合基础 Java 集合框架是一个强大的工具,它提供了一套标准化的接口和类,用于存储和操作集合数据。Collection 接口是这个框架的核心,它定义了一系列通用的集合操作。 2.Collection接口方法 …

既然有HTTP协议,为什么还要有RPC?

既然有HTTP协议,为什么还要有RPC? ​ 既然有HTTP协议,为什么还要有RPC? 有点既生瑜何生亮的味道。 第一次接触RPC我就很懵,平时我HTTP协议用得好好的,为什么还需要RPC协议? 于是我去百度&am…

最详细!适合AI大模型零基础入门的学习路线+学习方法+学习资料,全篇干货,建议收藏!

前言 随着ChatGPT的横空出世,大模型时代正式来临。千亿甚至万亿参数的大模型陆续出现,各大企业、高校纷纷推出自己的大模型,这标志着通用智能时代的到来。对于零基础的初学者来说,如何快速入门AI大模型,抓住这个时代的…

通过管道和共享存储映射实现进程通信

1.IPC方法 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区&a…

物联网智能项目全面解析

目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…

孩子英语不好,能学编程吗?

随着编程教育的普及,越来越多的家长开始关注孩子的编程学习。然而,不少家长提出了这样的疑问:“孩子的英语不好,是否还能学编程?”毕竟,编程语言是基于英语的,代码中也充斥着大量的英语单词和短…

数据结构-3.6.队列的链式实现

队列可以理解为单链表的阉割版&#xff0c;相比单链表而言&#xff0c;队列只有在添加和删除元素上和单链表有区别 一.队列的链式实现&#xff1a; 1.图解&#xff1a; 2.代码&#xff1a; #include<stdio.h> ​ typedef struct LinkNode //链式队列结点 {int data;st…

【韩顺平Java笔记】第1章

0-1可以看视频&#xff0c;下面记录主要内容 2. 就业方向 Java基础又叫JavaSE&#xff0c;Java有三个主要的就业方向&#xff1a; JavaEE软件工程师&#xff1a;电商&#xff0c;团购&#xff0c;众筹&#xff0c;sns&#xff08;社交网络&#xff09;&#xff0c;教育&…

纠删码参数自适应匹配问题ECP-AMP实验方案(一)

摘要 关键词&#xff1a;动态参数&#xff1b;多属性决策&#xff1b;critic权重法&#xff1b;DBSCA聚类分析 引言 云服务存储系统是一种基于互联网的数据存储服务&#xff0c;它可以为用户提供大规模、低成本、高可靠的数据存储空间。云服务存储系统的核心技术之一是数据容…

winsoft公司Utils组件功能简介

Winsoft Utils Library 2.3 是一个为 Delphi 和 C Builder&#xff08;版本 7 到 12 Athens&#xff09;设计的实用工具库。它提供了一系列组件和类&#xff0c;旨在简化和增强开发过程。以下是一些主要功能和特点&#xff1a; 1.组件集合&#xff1a;包含多种实用组件&#x…

AB plc设备数据 转profinet IO项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 网关采集AB PLC数据 2 5 用PROFINET IO协议转发数据 4 6 案例总结 7 1 案例说明 设置网关采集AB PLC数据把采集的数据转成profinet IO协议转发给其他系统。 2 VFBOX网关工作原理 VFBOX网关是协议转换网关&#xff0…

如果再回到从前——备忘录模式

文章目录 如果再回到从前——备忘录模式如果再给我一次机会……游戏存进度备忘录模式备忘录模式基本代码游戏进度备忘 如果再回到从前——备忘录模式 如果再给我一次机会…… 时间&#xff1a;5月6日18点  地点&#xff1a;小菜、大鸟住所的客厅  人物&#xff1a;小菜、…

Sharding-JDBC笔记03-分库分表代码示例

文章目录 一、水平分库1. 将原有order_db库拆分为order_db_1、order_db_22. 分片规则修改分片策略standardcomplexinlinehintnone 3. 插入测试4. 查询测试5. 使用分库分片键查询测试总结 二、公共表1. 创建数据库2. 在Sharding-JDBC规则中修改3. 数据操作4. 字典操作测试5. 字典…

Linux线程-POSIX信号量与锁以及条件变量

POSIX信号量 POSIX没有元素这个概念相比于SYSTEM-V更简洁&#xff0c;POSIX不一定适用老版本&#xff1b;二者都是系统范畴&#xff0c;都需要手动删除&#xff0c;POSIX相关函数属于线程库&#xff0c;所有编译时需要末尾加上-lpthread选项 POSIX POSIX有名信号量 主要用于进…

华为 HCIP-Datacom H12-821 题库 (29)

&#x1f423;博客最下方微信公众号回复题库,领取题库和教学资源 &#x1f424;诚挚欢迎IT交流有兴趣的公众号回复交流群 &#x1f998;公众号会持续更新网络小知识&#x1f63c; 1.BFD 为确保两端系统都知道状态的变化&#xff0c;在BFD 状态机的建立和拆除时都采用三次握手…

【LeetCode:75. 颜色分类 + 快速排序】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

图像超分辨率(SR)

图像超分辨率&#xff08;Image Super-Resolution, SR&#xff09;是一种图像处理技术&#xff0c;旨在从低分辨率&#xff08;LR&#xff09;图像中恢复出高分辨率&#xff08;HR&#xff09;图像。这种技术通过增加图像中的细节和清晰度来提高图像的视觉质量&#xff0c;从而…