GOLANG 接口

接口

模块化

目录

1.接口

一、接口的概念 01:09
1. 接口的概念 01:38
1)接口的概念理解 04:26
  • 接口的基本概念

    • 语言类型差异

      • 强类型语言(如Go)有明确的接口概念

      • 弱类型语言(如JavaScript)较少使用接口概念

    • 抽象特性:接口是非常抽象的概念,仅通过文字描述难以理解,需要结合实例

    • 设计哲学:接口设计不分对错,只有适用场景不同(类比"小孩才分对错,大人只看利弊")

  • 接口的实践应用

    • 网络请求示例

      • 实现步骤

        • 使用http.Get()获取网页内容

        • 处理可能的错误(panic(err))

        • 使用ioutil.ReadAll()读取响应体

        • 最后关闭响应体(defer resp.Body.Close())

      • 代码优化:将网络请求功能封装为retrieve(url string) string函数

    • 解耦设计

      • 模块分离

        • main函数负责参数解析和结果输出

        • retrieve函数专注于获取网络内容

      • 类型转换:将[]byte转换为string返回,简化调用方处理

  • 接口的实际应用

    • 团队协作场景

      • 基础设施团队

        • 定义Retriever结构体

        • 实现Get(url string) string方法

        • 负责真实的网络请求功能

      • 测试团队

        • 定义同名Retriever结构体

        • 实现相同方法但返回模拟数据(如"fake content")

    • 接口定义与使用

      • 接口语法:

    • 动态绑定

      • Go语言接口实现是隐式的

      • 只要类型实现了接口定义的所有方法,就自动满足该接口

      • 无需显式声明实现关系(与Java不同)

    • 解耦优势

      • 调用方只需关心接口能力,不依赖具体实现

      • 可轻松切换不同实现(如测试/生产环境)

二、知识小结
知识点 核心内容 考试重点/易混淆点 难度系数
接口的概念 接口是抽象的概念,用于模块解耦和抽象设计 强类型语言与弱类型语言对接口的理解差异 ⭐⭐⭐⭐
Go语言接口实现 无需显式声明实现接口,只要方法匹配即可 鸭子类型特性与传统OOP语言的区别 ⭐⭐⭐⭐
依赖管理 第三方库引入和版本控制方法 模块依赖与程序内部依赖的区分 ⭐⭐⭐
工程化开发 从代码片段到大型工程的过渡 模块化、可配置性和测试的重要性 ⭐⭐⭐⭐
并发编程 Go语言并发特性预告 并发与并行的概念区分 ⭐⭐⭐⭐⭐
错误处理 错误处理最佳实践 错误忽略(_)与正式处理的区别 ⭐⭐⭐
HTTP请求 http.Get和ioutil.ReadAll的使用 Response Body必须关闭的内存管理 ⭐⭐⭐
代码解耦 通过接口降低模块耦合度 具体实现与抽象定义的分离 ⭐⭐⭐⭐
测试替身 Testing团队实现的Mock Retriever 生产环境与测试环境的切换实现 ⭐⭐⭐
命令行参数 命令行参数解析库的使用 参数传递与程序输入的规范化 ⭐⭐
一、接口 00:06
  • 语言特性: Go语言是面向接口的编程语言,与传统面向对象语言不同,它仅支持封装,通过接口完成继承和多态的功能

  • 接口特点: Go语言的接口比传统语言更灵活,形式上类似其他语言的interface定义(如包含traverse函数),但使用方式有本质区别

1. duck typing 01:07
1)例题:鸭子是不是鸭子 01:27
  • 类型系统对比

    :

    • 传统类型系统:通过内部结构判断(如生物学分类:脊索动物门→脊椎动物亚门→鸟纲→雁形目)

    • Duck typing:通过外部行为判断("像鸭子走路,像鸭子叫,那么就是鸭子")

  • 核心思想

    : 描述事物的外部行为而非内部结构,大黄鸭案例中:

    • 传统视角:不是鸭子(无生命特征)

    • Duck typing视角:是鸭子(具备鸭子外观特征)

    • 使用者视角:取决于使用场景(儿童认为是鸭子,吃货认为不是)

  • Go语言特性

    : 严格说属于结构化类型系统,类似duck typing但不完全相同

    • 区别点:Go是编译时绑定,传统duck typing要求动态绑定

    • 共同点:都关注外部行为描述,不关心具体实现结构

2)Python中的Duck Typing 05:39
  • 实现方式

    :

    • 函数定义时不声明参数类型(如download函数接收retriever参数)

    • 运行时检查参数是否实现所需方法(如get方法)

  • 缺点

    :

    • 需通过注释说明接口要求

    • 运行时才能发现类型错误

  • 示例特点

    :

    • 灵活性高:任何实现get方法的对象都可作为参数

    • 隐式约定:依赖开发者自觉实现所需方法

3)C++中的Duck Typing 07:36
  • 实现方式

    : 通过模板(template)支持

    • 模板参数R只需实现get方法

    • 编译时检查类型约束

  • 改进点: 相比Python的运行时检查,编译时就能发现错误

  • 保留问题

    :

    • 仍需注释说明接口要求

    • 编码时无法获得类型提示

4)Java中的类似代码 09:35
  • 实现方式

    : 通过接口继承(extends Retriever)强制约束

    • 参数必须显式实现指定接口

    • 编译时严格类型检查

  • 优点: 无需注释说明,类型要求明确

  • 缺点

    :

    • 不够灵活:必须显式实现接口

    • 无法组合多个接口要求(如同时需要Readable和Appendable)

    • 解决方案复杂(如Apache Polygene项目)

5)Go语言的Duck Typing 11:25
  • 设计优势

    :

    • 保留Python/C++的灵活性:不要求显式声明实现接口

    • 具备Java的类型检查:编译时验证接口实现

    • 支持接口组合:可同时要求多个接口能力

  • 核心特点

    :

    • 接口实现是隐式的

    • 类型系统在编译期完成检查

    • 完美平衡灵活性与安全性

二、知识小结
知识点 核心内容 考试重点/易混淆点 难度系数
狗语言的接口特点 面向接口编程,与传统面向对象不同,不支持复杂继承和多态,主要通过接口完成功能 接口与传统继承的区别 ⭐⭐⭐
鸭子类型(Duck Typing)概念 关注外部行为而非内部结构,以大黄鸭为例说明类型系统的不同视角 传统类型系统与鸭子类型的本质区别 ⭐⭐⭐⭐
Python中的鸭子类型 运行时动态检查对象方法,需通过注释说明接口要求 运行时错误风险 ⭐⭐
C++中的鸭子类型 通过模板实现,编译时检查方法存在性 仍需注释说明接口要求 ⭐⭐⭐
Java的接口限制 必须显式实现接口,无法组合多个接口 类型安全但灵活性差 ⭐⭐⭐⭐
狗语言的接口优势 支持接口组合+编译时类型检查+无需显式声明实现 如何平衡灵活性与类型安全 ⭐⭐⭐⭐⭐
结构化类型系统 狗语言属于编译时绑定的结构化类型系统,类似但不完全等同鸭子类型 动态绑定与静态绑定的区别 ⭐⭐⭐⭐
一、接口的定义 00:00
  • 基本概念:接口定义了使用者(如download函数)和实现者(如Retriever)之间的契约关系

  • 角色划分

    • 使用者:调用接口方法的代码(如download函数)

    • 实现者:提供接口方法具体实现的代码(如Retriever)

1. java中的类似代码 00:15
  • 示例代码:<R extends Retriever>String download(R r) {return r.get("<www.imooc.com>");}

  • 调用关系:download函数作为使用者,调用Retriever接口的get方法

2. go语言的接口的定义 01:35
  • 关键区别

    • 传统面向对象:由实现者定义接口(如File类声明实现Readable和Appendable)

    • Go语言:由使用者定义接口(如Retriever接口由download函数的使用者定义)

  • Duck Typing:只要类型实现了接口定义的所有方法,就被视为实现了该接口

1)例题:接口定义使用案例 01:59
  • 大黄鸭比喻

    • 小孩视角:大黄鸭是鸭子

    • 吃货视角:大黄鸭不是鸭子

  • 接口定义权:接口的"身份"由使用者决定,而非实现者

2)接口的实现 03:32
  • 接口实现示例

    03:39

    • 接口定义:

    • 使用者代码:

    • 实现要点:实现者不需要显式声明实现接口,只需实现所有方法

  • 接口实现示例

    06:52

    • Mock实现:

    • 真实实现:

    • 资源管理:使用http响应后必须调用resp.Body.Close()释放资源

二、知识小结
知识点 核心内容 考试重点/易混淆点 难度系数
接口定义 由使用者定义接口(与传统面向对象不同),使用者规定接口方法(如retriever需实现get方法) 传统OOP:实现者声明接口(如Readable);Go语言:使用者定义需求 ⭐⭐
接口实现 隐式实现(无需显式声明implements),只需实现接口全部方法即可(如mockRetriever实现get) 实现者代码中无需出现接口名,仅需方法匹配 ⭐⭐
使用者与实现者角色 download为使用者,依赖retriever.get;mockRetriever/httpRetriever为实现者 使用者定义抽象,实现者提供具体逻辑
HTTP请求实现 httpRetriever通过http.Get获取数据,需处理response.Body.Close() 易错点:未关闭响应体会导致资源泄漏 ⭐⭐⭐
大黄鸭比喻 接口的抽象性:使用者决定对象角色(小孩视作鸭子,吃货视作食物) 强调接口的多态性和上下文依赖
一、接口的值类型 00:06
1. 值类型 00:31
  • 实现机制: Go语言中所有类型都是值类型,接口变量内部包含实现者的类型和值,而不仅仅是引用或指针

  • 存储方式: 当赋值具体类型时,会将对象拷贝进接口变量的"肚子"里;当赋值指针时,则存储指针指向的结构

2. 值类型的指针 02:11
  • 指针接收者: 当struct很大时,可通过指针接收者避免拷贝,如func (r *Retriever) Get()

  • 赋值要求: 指针接收者必须使用指针方式赋值,否则会编译错误"does not implement"

  • 值接收者: 值接收者更灵活,既可使用值也可使用指针方式赋值

3. 值类型的判断 04:24
  • type switch: 使用switch v := r.(type)可判断接口变量具体类型

  • case匹配: 需匹配具体类型名,如case mock.Retriever或case *real.Retriever

4. 值类型的断言 06:04
  • 严格断言: realRetriever := r.(*real.Retriever)会直接panic如果类型不匹配

  • 安全断言: 使用if mockRetriever, ok := r.(mock.Retriever); ok可避免panic

  • 转换方式: 通过.(具体类型)将interface{}转换为具体类型

5. 接口变量里面有什么 09:12
  • 内部结构: 包含实现者类型和实现者值/指针

  • 使用建议: 几乎不需要使用接口指针,因为接口变量本身可包含指针

  • 传递方式: 接口变量采用值传递,但因内含指针通常不需要取地址

6. 查看接口变量 11:33
  • 打印方式: 使用fmt.Printf("%T %v\n", r, r)可查看类型和值

  • 通用类型: interface{}表示任何类型,类似Python的object

7. 应用案例 11:58
1)例题:任何类型接口变量使用
  • 实现要点: 使用type Queue []interface{}支持任意类型元素

  • 类型转换: 在方法内部通过head.(int)将interface{}转换为具体类型

  • 运行时检查: 如果类型不匹配会在运行时panic

2)例题:接口变量支持任何类型 12:32
  • 参数限定: 方法参数声明为int可限制输入类型

  • 返回值处理: 返回值也需要做类型断言return head.(int)

  • 编译检查: 类型不匹配会在编译时报错

3)例题:接口变量限定类型 13:15
  • 内部限定: 在方法实现中强制转换*q = append(*q, v.(int))

  • 错误处理: 错误类型传入会导致运行时panic

  • 最佳实践: 推荐在方法签名中限定类型而非在实现中强制转换

二、知识小结
知识点 核心内容 考试重点/易混淆点 难度系数
Go语言接口实现 接口变量包含类型和值两部分,值可以是实际值或指针 指针接收者与值接收者的区别(指针接收者只能以指针方式使用) ⭐⭐⭐
接口类型断言 通过.(type)进行类型判断,有严格模式(不加OK)和非严格模式(加OK) 类型断言失败处理(需配合OK参数避免panic) ⭐⭐⭐⭐
空接口应用 interface{}表示任意类型,类似Python的万能容器 类型安全风险(运行时才能发现类型错误) ⭐⭐
队列泛型实现 通过[]interface{}实现支持任意类型的队列 类型约束技巧(函数参数限定 vs 运行时强制转换) ⭐⭐⭐⭐
接口值传递特性 接口变量采用值传递但内含指针,通常不需使用接口指针 指针接收者与值接收者在接口中的存储差异 ⭐⭐⭐
类型检查方法 switch v := x.(type)和fmt.Printf("%T")两种类型探查方式 type switch对指针类型的处理 ⭐⭐⭐
一、接口的组合 00:00
1. 接口的组合概念 00:16
  • 使用者定义接口:在Go语言中,接口可以由使用者灵活定义,不仅限于单一接口,还可以通过组合多个接口来创建新的接口类型。

  • 组合方式:一个接口可以包含其他接口的方法集合,形成更大的接口。例如一个文件接口可以同时包含读写功能,一个网络请求接口可以同时包含获取和提交功能。

2. 接口的组合示例 02:36
1)接口实现者 02:47
  • 隐式实现:Go语言中实现接口不需要显式声明,只需实现接口的所有方法即可。例如mock.Retriever结构体实现了Poster和Retriever接口的所有方法。

  • 指针接收者:当需要修改接收者状态时,应该使用指针接收者实现方法(如func (r *Retriever) Post()),否则修改不会生效。

2)接口的使用者 03:29
  • 组合定义:可以定义包含多个接口的新接口,如type RetrieverPoster interface { Retriever; Poster },表示同时具备获取和提交能力的对象。

  • 方法调用:组合接口可以调用所有被组合接口的方法,如s.Get()和s.Post()。

3)接口的使用者筛选示例 04:01
  • 参数传递:函数可以接收组合接口作为参数,如func session(s RetrieverPoster),这样传入的参数必须同时实现所有被组合的接口。

  • 内容修改:通过接口方法修改内部状态时,必须确保方法使用指针接收者,否则修改不会持久化。

4)接口的使用者试用示例 05:59
  • 实际应用:演示了如何通过组合接口实现先提交后获取的完整流程,最终输出修改后的内容"another faked imooc.com"。

  • 类型断言:使用类型断言r.(*mock.Retriever)可以检查接口值的具体类型,并访问具体类型的方法和字段。

3. 接口的组合应用案例 08:21
1)标准库中的接口组合 08:39
  • IO接口:标准库中大量使用接口组合,如io.ReadWriteCloser组合了Reader、Writer和Closer三个接口。

  • 设计优势:这种设计提供了极大的灵活性,使用者可以根据需要选择合适粒度的接口,实现者只需关注具体方法实现。

  • 常见组合:包括ReadWriter、ReadCloser、WriteCloser等多种两两组合,满足不同场景的需求。

二、知识小结
知识点 核心内容 考试重点/易混淆点 难度系数
接口定义方法 使用者可自定义接口,包括单一接口和组合接口 接口组合的语法和实现方式 ⭐⭐
接口组合 通过组合多个小接口创建新接口(如Retriever+Post组合成新接口) 指针接收者与值接收者在接口实现中的区别 ⭐⭐⭐
Mock实现示例 MockRetriever同时实现Retriever和Post接口 无需显式声明实现,只需包含所需方法 ⭐⭐
标准库接口组合 io包中的ReadWriter、ReadWriteCloser等组合接口 理解标准库中常见接口组合模式 ⭐⭐
接口灵活性 Go语言接口的鸭子类型特性,实现者只需关注方法实现 使用者可自由定义所需接口能力组合 ⭐⭐⭐
一、常用系统接口 00:04
1. Stringer接口 00:13
  • 功能: 相当于Java的toString方法,定义值的原生格式

  • 实现方式: 任何实现了String()方法的类型都实现了Stringer接口

  • 使用场景: 用于格式化打印值,当值被传递给接受字符串的格式时自动调用

1)例题:模拟器string方法实现 00:35
  • 实现示例:

  • 格式化效果: 当打印Retriever实例时,会自动调用String()方法输出格式化内容

  • 对比说明: 未实现Stringer接口的类型会使用系统默认的格式化输出

2)inspect方法介绍 01:17
  • 功能: 检查Retriever类型并打印相关信息

  • 类型判断: 使用type switch区分mock.Retriever和real.Retriever

  • 输出优化: 通过调整打印格式使输出更清晰可读

  • 实际输出:

2. Reader和Writer接口 03:00
  • 核心功能

    :

    • Reader: 从数据源读取数据到字节切片

    • Writer: 将字节切片写入目标

  • 设计优势: 抽象了I/O操作,不限于文件操作,可用于网络、内存等多种场景

  • 实现要求: 必须正确处理返回值和错误情况

1)os包open方法 03:42
  • 返回值: 返回File结构体指针,实现了多个I/O接口

  • 接口实现: File自动实现了Reader、Writer等接口,无需显式声明

  • 设计理念: Go语言中接口实现是隐式的,只要实现了接口方法即视为实现了接口

2)bufio包NewScanner方法 04:47
  • 参数要求: 接受io.Reader接口类型,不限定具体实现

  • 灵活性: 可以处理文件、网络连接、字符串等多种输入源

  • 使用示例:

  • 例题:printFile方法改造

    06:12

    • 改进点: 将参数从文件名改为io.Reader接口

    • 优势

      :

      • 可以处理文件(strings.NewReader)

      • 可以处理字符串(bytes.NewReader)

      • 提高代码复用性和灵活性

    • 实现代码:

3. 内容总结 08:41
  • Stringer

    :

    • 相当于toString功能

    • 通过实现String()方法自定义输出格式

  • Reader/Writer

    :

    • 抽象I/O操作的通用接口

    • 使代码能处理多种数据源

    • 广泛应用于标准库中的I/O相关函数

  • 设计原则

    :

    • 接口由使用者定义

    • 实现者只需关注方法实现

    • 通过接口组合提高灵活性

二、知识小结
知识点 核心内容 考试重点/易混淆点 难度系数
Go语言接口组合 通过组合接口(如Reader+Writer=ReadWriter)实现功能复用,使用者定义组合,实现者无需关注接口层级 接口组合的隐式实现机制(无需显式声明) ⭐⭐
Stringer接口 类似Java的toString(),通过实现String() string方法自定义类型输出格式 fmt.Sprintf格式化输出与默认Stringer行为对比 ⭐⭐
Reader/Writer接口 对文件/网络/字符串等读写操作的抽象,关键系统接口(如fmt.Fprintf依赖Writer) strings.NewReader/bytes.NewReader将字符串/字节流转为Reader ⭐⭐⭐
接口实现与使用分离 实现者仅需定义方法(如Read()),使用者决定接口匹配(如io.Reader) IDE辅助提示与隐式接口满足的陷阱 ⭐⭐⭐
实战应用示例 printFileContents函数通过接收Reader参数兼容文件/字符串/网络等多种输入源 接口抽象带来的扩展性优势(如替换数据源无需修改函数) ⭐⭐

Go 语言接口核心知识点总结

Go 语言的接口系统是其独特且强大的特性,核心要点包括:

  1. 隐式实现 :无需显式声明implements,只要方法集匹配即视为实现接口

  2. 鸭子类型:"像鸭子走路、叫,就视为鸭子",关注行为而非类型

  3. 接口组合 :通过组合小接口形成新接口(如Reader+Writer=ReadWriter

  4. 使用者定义:接口通常由使用者定义,而非实现者

  5. 接口值结构:包含类型信息和值信息两部分

  6. 类型断言 :用于检查接口实际类型,支持安全模式(带ok

  7. 空接口interface{}可表示任意类型,类似万能容器

以下通过一个综合示例代码,展示 Go 语言接口的核心特性:

复制代码
package main
​
import (
    "fmt"
    "io"
    "os"
    "strings"
)
​
// 1. 接口由使用者定义 - 定义一个数据获取接口
type Retriever interface {
    Get(url string) (string, error)
}
​
// 2. 接口组合示例 - 扩展为可发布数据的接口
type Poster interface {
    Post(url string, data string) (string, error)
}
​
// 组合接口
type RetrieverPoster interface {
    Retriever
    Poster
}
​
// 3. 实现者无需显式声明实现接口
// HTTP数据获取器
type HTTPRetriever struct {
    UserAgent string
}
​
func (h HTTPRetriever) Get(url string) (string, error) {
    // 简化实现,实际应使用http.Get
    return fmt.Sprintf("从%s获取数据(UserAgent: %s)", url, h.UserAgent), nil
}
​
// Mock数据获取器(用于测试)
type MockRetriever struct {
    Content string
}
​
func (m MockRetriever) Get(url string) (string, error) {
    return m.Content, nil
}
​
// 让MockRetriever同时实现Poster接口
func (m *MockRetriever) Post(url string, data string) (string, error) {
    m.Content = data
    return "发布成功", nil
}
​
// 4. 使用者代码 - 依赖接口而非具体实现
func download(r Retriever, url string) (string, error) {
    return r.Get(url)
}
​
func postData(rp RetrieverPoster, url string, data string) (string, error) {
    return rp.Post(url, data)
}
​
// 5. Stringer接口示例(类似toString)
func (h HTTPRetriever) String() string {
    return fmt.Sprintf("HTTPRetriever{UserAgent: %s}", h.UserAgent)
}
​
// 6. 类型断言示例
func inspect(r Retriever) {
    fmt.Printf("类型: %T, 值: %v\n", r, r)
    
    // 类型断言 - 安全模式
    if mock, ok := r.(MockRetriever); ok {
        fmt.Println("这是MockRetriever,内容:", mock.Content)
    } else if http, ok := r.(HTTPRetriever); ok {
        fmt.Println("这是HTTPRetriever,UserAgent:", http.UserAgent)
    }
}
​
// 7. 空接口示例 - 处理任意类型
func printAny(v interface{}) {
    // 使用type switch检查空接口类型
    switch v := v.(type) {
    case int:
        fmt.Printf("整数: %d\n", v)
    case string:
        fmt.Printf("字符串: %s\n", v)
    case Retriever:
        fmt.Printf("Retriever: %v\n", v)
    default:
        fmt.Printf("未知类型: %T\n", v)
    }
}
​
// 8. 标准库接口示例(io.Reader)
func readFromReader(reader io.Reader) string {
    buf := make([]byte, 1024)
    n, _ := reader.Read(buf)
    return string(buf[:n])
}
​
func main() {
    // 接口多态性展示
    var r Retriever
    
    r = HTTPRetriever{UserAgent: "Go-Getter/1.0"}
    content, _ := download(r, "https://example.com")
    fmt.Println("HTTP获取内容:", content)
    
    r = MockRetriever{Content: "模拟的网页内容"}
    content, _ = download(r, "https://example.com")
    fmt.Println("Mock获取内容:", content)
    
    // 类型检查
    inspect(r)
    
    // 接口组合使用
    mock := &MockRetriever{Content: "初始内容"}
    var rp RetrieverPoster = mock
    postData(rp, "https://example.com/post", "新的内容")
    fmt.Println("Post后的数据:", mock.Content)
    
    // Stringer接口效果
    fmt.Println("Stringer展示:", HTTPRetriever{UserAgent: "测试浏览器"})
    
    // 空接口示例
    printAny(42)
    printAny("Hello")
    printAny(r)
    
    // 标准库接口示例
    strReader := strings.NewReader("从字符串读取数据")
    fmt.Println("Reader内容:", readFromReader(strReader))
    
    file, _ := os.Open("interface_demo.go")
    defer file.Close()
    fmt.Println("文件内容预览:", readFromReader(file))
}
​
相关推荐
ahauedu3 小时前
AI资深 Java 研发专家系统解析Java 中常见的 Queue实现类
java·开发语言·中间件
韭菜钟3 小时前
在Qt中用cmake实现类似pri文件的功能
开发语言·qt·系统架构
闲人编程4 小时前
Python第三方库IPFS-API使用详解:构建去中心化应用的完整指南
开发语言·python·去中心化·内存·寻址·存储·ipfs
CTRA王大大5 小时前
【golang】制作linux环境+golang的Dockerfile | 如何下载golang镜像源
linux·开发语言·docker·golang
#include>5 小时前
【Golang】有关垃圾收集器的笔记
笔记·golang
zhangfeng11335 小时前
以下是基于图论的归一化切割(Normalized Cut)图像分割工具的完整实现,结合Tkinter界面设计及Python代码示
开发语言·python·图论
还梦呦6 小时前
2025年09月计算机二级Java选择题每日一练——第五期
java·开发语言·计算机二级
鱼鱼说测试7 小时前
postman接口自动化测试
开发语言·lua
從南走到北7 小时前
JAVA国际版东郊到家同城按摩服务美容美发私教到店服务系统源码支持Android+IOS+H5
android·java·开发语言·ios·微信·微信小程序·小程序