MST

星途 面试题库

面试题:Go语言接口实现复杂场景下的代码复用

在一个微服务架构的Go项目中,有多种数据存储方式(如MySQL、Redis、MongoDB),每种存储方式都需要实现数据的读写操作。请设计一个通用的接口来抽象读写操作,然后针对每种存储方式实现该接口,展示如何通过接口来复用数据读写相关的业务逻辑,并且说明在实际项目中这样做的好处以及可能遇到的问题。
37.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

1. 设计通用接口

// DataStore 定义数据存储接口
type DataStore interface {
    Read(key string) (interface{}, error)
    Write(key string, value interface{}) error
}

2. 针对每种存储方式实现接口

MySQL 实现

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

type MySQLStore struct {
    db *sql.DB
}

func NewMySQLStore(dsn string) (*MySQLStore, error) {
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, err
    }
    if err = db.Ping(); err != nil {
        return nil, err
    }
    return &MySQLStore{db: db}, nil
}

func (m *MySQLStore) Read(key string) (interface{}, error) {
    var result interface{}
    query := "SELECT value FROM your_table WHERE key =?"
    err := m.db.QueryRow(query, key).Scan(&result)
    if err != nil {
        return nil, err
    }
    return result, nil
}

func (m *MySQLStore) Write(key string, value interface{}) error {
    query := "INSERT INTO your_table (key, value) VALUES (?,?)"
    _, err := m.db.Exec(query, key, value)
    return err
}

Redis 实现

package main

import (
    "github.com/go-redis/redis/v8"
    "context"
    "fmt"
)

type RedisStore struct {
    client *redis.Client
    ctx    context.Context
}

func NewRedisStore(addr string, password string, db int) (*RedisStore, error) {
    client := redis.NewClient(&redis.Options{
        Addr:     addr,
        Password: password,
        DB:       db,
    })
    ctx := context.Background()
    _, err := client.Ping(ctx).Result()
    if err != nil {
        return nil, err
    }
    return &RedisStore{client: client, ctx: ctx}, nil
}

func (r *RedisStore) Read(key string) (interface{}, error) {
    result, err := r.client.Get(r.ctx, key).Result()
    if err != nil {
        return nil, err
    }
    return result, nil
}

func (r *RedisStore) Write(key string, value interface{}) error {
    err := r.client.Set(r.ctx, key, value, 0).Err()
    return err
}

MongoDB 实现

package main

import (
    "context"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "fmt"
)

type MongoDBStore struct {
    collection *mongo.Collection
    ctx        context.Context
}

func NewMongoDBStore(uri string, dbName string, collectionName string) (*MongoDBStore, error) {
    ctx := context.Background()
    client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
    if err != nil {
        return nil, err
    }
    collection := client.Database(dbName).Collection(collectionName)
    return &MongoDBStore{collection: collection, ctx: ctx}, nil
}

func (m *MongoDBStore) Read(key string) (interface{}, error) {
    var result bson.M
    filter := bson.M{"key": key}
    err := m.collection.FindOne(m.ctx, filter).Decode(&result)
    if err != nil {
        return nil, err
    }
    return result["value"], nil
}

func (m *MongoDBStore) Write(key string, value interface{}) error {
    document := bson.M{
        "key":   key,
        "value": value,
    }
    _, err := m.collection.InsertOne(m.ctx, document)
    return err
}

3. 通过接口复用业务逻辑示例

func ProcessData(store DataStore, key string, value interface{}) error {
    err := store.Write(key, value)
    if err != nil {
        return err
    }
    result, err := store.Read(key)
    if err != nil {
        return err
    }
    fmt.Printf("Read data: %v\n", result)
    return nil
}

4. 这样做的好处

  • 可维护性:每种存储方式的实现独立,修改一种存储方式的实现不会影响其他存储方式,也不会影响使用该接口的业务逻辑。例如如果要升级MySQL驱动版本,只需要修改MySQLStore的实现,而不会影响到使用RedisStore或MongoDBStore的部分。
  • 可扩展性:当需要添加新的数据存储方式时,只需要实现DataStore接口,就可以无缝集成到现有系统中。例如如果后续项目需要引入Elasticsearch存储,只需要按照接口规范实现Read和Write方法即可。
  • 代码复用:业务逻辑可以通过接口进行复用,避免在不同存储方式下重复编写相同的读写业务逻辑。例如上述的ProcessData函数,可以处理任何实现了DataStore接口的存储方式。

5. 可能遇到的问题

  • 性能差异:不同存储方式性能特性不同,在通用接口抽象下,可能难以针对每种存储进行极致的性能优化。例如Redis适合高速读写缓存数据,而MySQL适合复杂关系型数据处理,在通用接口下可能无法完全发挥各自的性能优势。
  • 接口兼容性:随着业务发展,接口可能需要扩展方法,但这可能导致所有实现类都需要修改。例如如果要在接口中添加一个Delete方法,那么MySQLStore、RedisStore、MongoDBStore都需要实现该方法。
  • 数据一致性:不同存储方式对数据一致性保证不同,在使用通用接口进行读写操作时,可能导致数据一致性问题。例如Redis的读写操作通常是原子性的,但MySQL在某些复杂事务场景下需要额外处理才能保证一致性。