Golang面试-开放型问题

1. 用golang写一个线程安全的单例(Singleton)类

go 复制代码
package main
import (
	"fmt"
	"sync"
)

type Singleton struct {
	data string
}

var instance *Singleton
var one sync.Once

// 返回单例实例
func GetInstance() *Singleton {
	one.Do(func() {
		instance = &Singleton{data: "初始化数据...."}
	})
	return instance
}
func main() {
	s1 := GetInstance()
	s2 := GetInstance()

	fmt.Println(s1 == s2) // 两个实例是相等的

	// 验证单例数据
	fmt.Println(s1.data)
	s2.data = "修改后的数据..."
	fmt.Println(s1.data)
}

2. 假设你工作的系统不支持事务性,你会如何从头开始实现它?

go 复制代码
package main

import (
	"fmt"
	"sync"
)

// Operation 是一个接口,定义了执行和回滚操作
type Operation interface {
	Execute() error
	Rollback() error
}

// WriteOperation 是一个简单的写操作,包含目标和数据
type WriteOperation struct {
	target  *string
	data    string
	oldData string
}

func (op *WriteOperation) Execute() error {
	op.oldData = *op.target
	*op.target = op.data
	return nil
}

func (op *WriteOperation) Rollback() error {
	*op.target = op.oldData
	return nil
}

// Transaction 管理一组操作
type Transaction struct {
	operations []Operation
}

func (t *Transaction) AddOperation(op Operation) {
	t.operations = append(t.operations, op)
}

func (t *Transaction) Commit() error {
	for _, op := range t.operations {
		if err := op.Execute(); err != nil {
			t.Rollback()
			return err
		}
	}
	return nil
}

func (t *Transaction) Rollback() {
	for i := len(t.operations) - 1; i >= 0; i-- {
		t.operations[i].Rollback()
	}
}

func main() {
	var wg sync.WaitGroup
	data1 := "initial1"
	data2 := "initial2"

	// 创建第一个事务
	t1 := &Transaction{}
	t1.AddOperation(&WriteOperation{target: &data1, data: "data1_1"})
	t1.AddOperation(&WriteOperation{target: &data2, data: "data1_2"})

	// 创建第二个事务
	t2 := &Transaction{}
	t2.AddOperation(&WriteOperation{target: &data1, data: "data2_1"})
	t2.AddOperation(&WriteOperation{target: &data2, data: "data2_2"})

	// 启动第一个事务
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := t1.Commit(); err != nil {
			fmt.Println("Transaction 1 failed:", err)
		} else {
			fmt.Println("Transaction 1 succeeded")
		}
	}()

	// 启动第二个事务
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := t2.Commit(); err != nil {
			fmt.Println("Transaction 2 failed:", err)
		} else {
			fmt.Println("Transaction 2 succeeded")
		}
	}()

	wg.Wait()

	fmt.Println("Final data1:", data1)
	fmt.Println("Final data2:", data2)
}

3. 创建一个有害的全局对象并说明 问题 并修复它

go 复制代码
package main

import (
	"fmt"
)

// 全局对象
var config = map[string]string{
	"mode": "development",
}

// 修改配置的函数
func setMode(mode string) {
	config["mode"] = mode
}

// 获取配置的函数
func getMode() string {
	return config["mode"]
}

func main() {
	fmt.Println("Initial mode:", getMode())

	// 在其他地方修改配置
	setMode("production")
	fmt.Println("Mode after change:", getMode())

	// 假设在另一个地方依赖于配置的某个函数
	runService()
}

func runService() {
	// 假设这个函数依赖于配置的 mode
	mode := getMode()
	if mode == "production" {
		// 这里打印了mode1 全局对象被改变了,这可能会导致意外的副作用和难以追踪的错误。
		fmt.Println("Running in production mode1")
	} else {
		fmt.Println("Running in development mode2")
	}
}

4. golang中空对象模式(Null Object Pattern)的目的是什么?

空对象模式的目的和好处

  1. 减少空值检查:通过使用空对象,可以减少或消除代码中对空值的显式检查,从而使代码更清晰。
  2. 提供默认行为:空对象可以提供默认的行为,避免在处理空值时出现的空指针异常(nil pointer dereference)。
  3. 统一接口:空对象实现了与其他对象相同的接口,确保了代码的一致性和可替换性。
  4. 简化代码逻辑:使用空对象可以简化业务逻辑中的条件分支,减少代码复杂性。
    例子
go 复制代码
package main

import "fmt"

// Logger 是一个日志记录器接口
type Logger interface {
	Info(message string)
	Error(message string)
}

// ConsoleLogger 是一个具体的日志记录器实现,将日志打印到控制台
type ConsoleLogger struct{}

func (l *ConsoleLogger) Info(message string) {
	fmt.Println("INFO:", message)
}

func (l *ConsoleLogger) Error(message string) {
	fmt.Println("ERROR:", message)
}

// NullLogger 是一个空日志记录器实现,不执行任何操作
type NullLogger struct{}

func (l *NullLogger) Info(message string)  {}
func (l *NullLogger) Error(message string) {}

func main() {
	// 使用 ConsoleLogger
	var logger Logger = &ConsoleLogger{}
	logger.Info("This is an info message")
	logger.Error("This is an error message")

	// 使用 NullLogger
	logger = &NullLogger{}
	logger.Info("This message will not be logged")
	logger.Error("This error will not be logged")
}

5. 为什么组合(Composition)比继承(Inheritance)更好?

在软件设计中,组合(Composition)通常被认为比继承(Inheritance)更好,主要原因包括以下几个方面:

松耦合:

继承导致子类和父类之间的紧密耦合,子类的实现依赖于父类的实现,任何对父类的修改都可能影响到子类。

组合通过将行为委托给独立的对象,使类之间的耦合度降低。组合允许对象在运行时被替换,从而提高了系统的灵活性和可维护性。

灵活性:

继承是一种静态关系,在编译时决定,并且一个类只能继承一个父类(在Go中是这样)。这限制了类的扩展方式。

组合允许将多个独立的功能组合在一起,通过组合不同的组件,可以动态地创建新的行为和功能。

代码重用:

继承往往导致代码重复和冗余,因为子类会继承父类的所有行为,即使某些行为对子类是不必要的。

组合通过将通用功能提取到独立的组件中,允许这些组件在不同的类中重用,从而减少代码重复。

遵循设计原则:

组合更符合"组合优于继承"的设计原则(Composition over Inheritance)以及单一职责原则(Single Responsibility Principle),即每个类应该只有一个职责。

继承容易导致违反单一职责原则,因为子类会继承父类的所有行为,这些行为可能与子类的职责不完全匹配。

避免继承层次复杂性:

随着继承层次的加深,代码的复杂性和维护成本会显著增加,子类必须理解和维护整个继承链的行为。

组合通过将功能模块化,避免了复杂的继承层次,从而简化了系统的设计和维护。

go 复制代码
package main
// 继承
import "fmt"

// Animal 是一个基类,定义了动物的通用行为
type Animal struct{}

func (a *Animal) Eat() {
	fmt.Println("Animal is eating")
}

// Dog 继承自 Animal,并添加了新的行为
type Dog struct {
	Animal
}

func (d *Dog) Bark() {
	fmt.Println("Dog is barking")
}

func main() {
	dog := &Dog{}
	dog.Eat()  // 从 Animal 继承的方法
	dog.Bark() // Dog 自己的方法
}
go 复制代码
package main
// 组合
import "fmt"

// Eater 接口定义了吃的行为
type Eater interface {
	Eat()
}

// Barker 接口定义了叫的行为
type Barker interface {
	Bark()
}

// Animal 实现了 Eater 接口
type Animal struct{}

func (a *Animal) Eat() {
	fmt.Println("Animal is eating")
}

// Dog 通过组合 Animal 和 Barker 实现了所需的行为
type Dog struct {
	Eater
	Barker
}

// DogBarker 实现了 Barker 接口
type DogBarker struct{}

func (b *DogBarker) Bark() {
	fmt.Println("Dog is barking")
}

func main() {
	animal := &Animal{}
	barker := &DogBarker{}

	dog := &Dog{
		Eater:  animal,
		Barker: barker,
	}

	dog.Eat()  // 通过组合的 Animal 的方法
	dog.Bark() // 通过组合的 DogBarker 的方法
}

6 你是如何处理依赖关系地狱(Dependency Hell)的?

在 Go 中,依赖关系地狱(Dependency Hell)通常指的是由于项目中依赖的外部包或库过多,版本管理和兼容性问题导致的各种麻烦。为了解决这些问题,Go 提供了一些工具和最佳实践来管理依赖关系。

  1. 使用 Go Modules
  2. 使用语义版本控制(Semantic Versioning)
  3. 使用 Go Modules 代理
  4. 版本锁定(Vendoring)
  5. 保持依赖项的最小化
  6. 定期更新和审查依赖项
相关推荐
凤枭香7 分钟前
Python Scikit-learn简介
开发语言·python·机器学习·scikit-learn
ThetaarSofVenice11 分钟前
Java从入门到放弃 之 泛型
java·开发语言
WHabcwu25 分钟前
统⼀异常处理
java·开发语言
Mr__vantasy27 分钟前
数据结构(初阶6)---二叉树(遍历——递归的艺术)(详解)
c语言·开发语言·数据结构·算法·leetcode
寒雒29 分钟前
【Python】实战:实现GUI登录界面
开发语言·前端·python
山山而川粤34 分钟前
废品买卖回收管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·后端·学习·mysql
李先静1 小时前
AWTK-WEB 快速入门(1) - C 语言应用程序
c语言·开发语言·前端
zmd-zk1 小时前
flink学习(3)——方法的使用—对流的处理(map,flatMap,filter)
java·大数据·开发语言·学习·flink·tensorflow
圆蛤镇程序猿1 小时前
【什么是SpringMVC】
开发语言
Domain-zhuo2 小时前
JS对于数组去重都有哪些方法?
开发语言·前端·javascript