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. 定期更新和审查依赖项
相关推荐
AAA修煤气灶刘哥4 分钟前
别再懵了!Spring、Spring Boot、Spring MVC 的区别,一篇讲透
后端·面试
Spider_Man11 分钟前
面试官的 JS 继承陷阱,你能全身而退吗?🕳️
前端·javascript·面试
pepedd86412 分钟前
探究js继承实现方式-js面向对象的基础
前端·面试·trae
ZLRRLZ18 分钟前
【C++】C++11
开发语言·c++
全栈软件开发28 分钟前
PHP域名授权系统网站源码_授权管理工单系统_精美UI_附教程
开发语言·ui·php·php域名授权·授权系统网站源码
誰能久伴不乏33 分钟前
Qt 动态属性(Dynamic Property)详解
开发语言·qt
似水流年流不尽思念39 分钟前
Spring MVC 中的 DTO 对象的字段被 transient 修饰,可以被序列化吗?
后端·面试
似水流年流不尽思念43 分钟前
为啥 HashMap 中的 table 也被 transient 修饰?其目的是什么?
后端·面试
枫叶丹41 小时前
【Qt开发】常用控件(四)
开发语言·qt
草莓熊Lotso1 小时前
《吃透 C++ 类和对象(中):const 成员函数与取地址运算符重载解析》
c语言·开发语言·c++·笔记·其他