在 Java 或 .NET 这样的面向对象语言里,基类的方法可以调用子类重写的方法,这是通过 虚方法表(VTable) 机制实现的。例如,Java 代码如下:
abstract class BaseModel {
// 子类必须实现的方法
public abstract String getTableName();
// 生成 SQL 查询
public String queryOne() {
return "SELECT * FROM " + getTableName() + " LIMIT 1";
}
}
class UserModel extends BaseModel {
private String tableName;
public UserModel(String tableName) {
this.tableName = tableName;
}
@Override
public String getTableName() {
return tableName;
}
}
public class Main {
public static void main(String[] args) {
BaseModel user = new UserModel("user");
System.out.println(user.queryOne()); // 输出: SELECT * FROM user LIMIT 1
}
}
Java 允许基类 BaseModel
直接调用 getTableName()
,并能正确调用子类 UserModel
实现的 getTableName()
,这是因为 Java 通过虚方法表进行动态调度。
Go的实现方式
Go 不是典型的面向对象语言,它没有继承,而是使用 组合 来复用代码,因此无法直接实现类似 Java 的动态方法调用。例如:
package main
import "fmt"
type Tabler interface {
TableName() string
}
type BaseModel struct {
tabler Tabler
}
func (b *BaseModel) QueryOne() string {
return fmt.Sprintf("SELECT * FROM %s LIMIT 1", b.tabler.TableName())
}
type UserModel struct {
BaseModel
}
func NewUserModel() *UserModel {
u := &UserModel{}
u.tabler = u // 把自身赋值给 BaseModel.tabler
return u
}
func (u *UserModel) TableName() string {
return "users"
}
func main() {
u := NewUserModel()
fmt.Println(u.QueryOne()) // 输出: SELECT * FROM users LIMIT 1
}
在 Go 里,BaseModel不能直接调用 TableName(),因为它并不知道子类 UserModel 的 TableName()。为了实现 Java 类似的效果,我们引入了 Tabler 接口,并在 NewUserModel() 里手动把 UserModel 赋值给 BaseModel 的 tabler 字段。
为什么 Go 这样设计?
Go 这样设计的原因主要是为了简化继承带来的复杂性,比如:
避免菱形继承问题(多个基类继承同一个父类,可能导致方法解析冲突)
去除 VTable,提高运行效率(Go 采用接口和组合实现动态行为)
显式地表达对象关系(在 Go 里,你必须手动赋值
tabler
,这样可以避免某些隐藏的继承行为)
结论
Java 通过继承和虚方法表(VTable)实现动态方法调用
Go 没有继承,采用组合 + 接口方式
在 Go 里,基类无法直接访问子类方法,必须使用接口来间接实现
如果可能,直接用接口代替继承
虽然 Go 不能像 Java 那样优雅地处理继承,但它通过接口提供了一种更显式、更可控的方式来实现类似功能。