Skip to content

类与接口

本指南介绍 Targo 中的类(class)和接口(interface)系统,它们是构建复杂应用的基础。

目标读者

  • 熟悉 TypeScript 类和接口的开发者
  • 想要理解 Targo 如何映射到 Go 结构体和接口的开发者
  • 需要在 Targo 中设计数据结构和行为契约的开发者

核心概念

在 Targo 中:

  • class 定义数据结构,编译为 Go 的 struct
  • interface 定义行为契约,编译为 Go 的 interface
  • class 默认是引用语义(指针),符合 TypeScript 开发者的直觉
  • interface 采用隐式实现,无需显式声明

类(Class)

基本定义

使用 class 关键字定义数据结构:

typescript
class User {
    ID: int64;
    Name: string;
    Email: string;
    Age: int;
}

编译为 Go:

go
type User struct {
    ID    int64
    Name  string
    Email string
    Age   int
}

字段可见性

Targo 提供两种方式控制字段可见性:

语法Go 输出可见性说明
name: stringName string公开默认大写(Go 公开)
#secret: stringsecret string私有# 前缀表示私有字段

公开字段(默认):

typescript
class User {
    Name: string;      // → Name string(公开)
    Age: int;          // → Age int(公开)
}

私有字段(使用 # 前缀):

typescript
class User {
    Name: string;       // 公开
    #password: string;  // 私有
    
    GetPassword(): string {
        return this.#password;  // 类内部可以访问
    }
    
    SetPassword(pwd: string): void {
        this.#password = pwd;
    }
}

let u = new User();
u.SetPassword("secret");
// u.#password;  // ❌ 外部不能直接访问

编译为 Go:

go
type User struct {
    Name     string
    password string  // 包内私有
}

func (u *User) GetPassword() string {
    return u.password
}

func (u *User) SetPassword(pwd string) {
    u.password = pwd
}

TypeScript 对比

Targo 不支持 TypeScript 的 private 关键字。如需私有字段,请使用 # 语法。

可选字段

使用 ? 标记可选字段,编译为 Go 指针类型:

typescript
class User {
    ID: int64;
    Name: string;
    Email?: string;    // 可选 → *string
    Phone?: string;    // 可选 → *string
}

编译为 Go:

go
type User struct {
    ID    int64
    Name  string
    Email *string
    Phone *string
}

创建实例

使用 new(推荐)

new T() 返回引用类型,映射到 Go 的 new(T)

typescript
let user = new User();  // User(实际是 *User)
user.Name = "Alice";
user.Age = 25;

编译为 Go:

go
user := new(User)
user.Name = "Alice"
user.Age = 25

使用字面量

使用对象字面量配合类型注解:

typescript
let user: User = {
    ID: 1,
    Name: "Alice",
    Email: "alice@example.com",
    Age: 25
};

编译为 Go:

go
user := &User{
    ID:    1,
    Name:  "Alice",
    Email: "alice@example.com",
    Age:   25,
}

构造函数

Targo 支持 TypeScript 风格的构造函数,编译为 Go 的工厂函数:

typescript
class User {
    Name: string;
    Age: int;
    CreatedAt: int64;
    
    constructor(name: string, age: int) {
        this.Name = name;
        this.Age = age;
        this.CreatedAt = time.Now().Unix();
    }
}

let u = new User("Alice", 25);

编译为 Go:

go
type User struct {
    Name      string
    Age       int
    CreatedAt int64
}

func NewUser(name string, age int) *User {
    result := &User{}
    result.Name = name
    result.Age = age
    result.CreatedAt = time.Now().Unix()
    return result
}

u := NewUser("Alice", 25)

保留函数名

当类定义了 constructor 时,编译器会生成 NewClassName 工厂函数。不要定义同名函数,否则会导致命名冲突。

需要多种初始化方式时,使用其他命名的工厂函数

typescript
class User {
    Name: string;
    Email: string;
    
    constructor(name: string, email: string) {
        this.Name = name;
        this.Email = email;
    }
}

// 自定义工厂函数(避免 NewUser 命名)
function UserFromEmail(email: string): User {
    let name = email.split("@")[0];
    return new User(name, email);
}

function UserWithDefaults(): User {
    return new User("Guest", "guest@example.com");
}

方法定义

方法定义在 class 内部,使用 this 引用当前实例:

typescript
class Point {
    X: float64;
    Y: float64;
    
    Distance(): float64 {
        return math.Sqrt(this.X * this.X + this.Y * this.Y);
    }
    
    Add(other: Point): Point {
        return { X: this.X + other.X, Y: this.Y + other.Y };
    }
}

let p = new Point();
p.X = 3.0;
p.Y = 4.0;
let d = p.Distance();  // 5.0

编译为 Go:

go
type Point struct {
    X float64
    Y float64
}

func (p *Point) Distance() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

func (p *Point) Add(other *Point) *Point {
    return &Point{X: p.X + other.X, Y: p.Y + other.Y}
}

方法接收器

默认:指针接收器(修改原对象)

值接收器:使用显式 this: Val<T> 参数(操作副本)

typescript
class Counter {
    Value: int;
    
    // 指针接收器(默认)- 修改原对象
    Increment(): void {
        this.Value++;
    }
    
    // 值接收器 - 操作副本
    Get(this: Val<Counter>): int {
        return this.Value;
    }
}

编译为 Go:

go
type Counter struct {
    Value int
}

func (c *Counter) Increment() {
    c.Value++
}

func (c Counter) Get() int {
    return c.Value
}

结构体标签(Struct Tags)

用于序列化、ORM 等场景,使用 @tag 装饰器:

typescript
class User {
    @tag("json", "user_id")
    @tag("db", "id")
    ID: int64;
    
    @tag("json", "user_name")
    Name: string;
    
    @tag("json", "email,omitempty")
    Email?: string;
    
    @tag("json", "-")
    Password: string;
}

编译为 Go:

go
type User struct {
    ID       int64   `json:"user_id" db:"id"`
    Name     string  `json:"user_name"`
    Email    *string `json:"email,omitempty"`
    Password string  `json:"-"`
}

结构体嵌入(Embedding)

Go 支持结构体嵌入,被嵌入类型的字段和方法会"提升"到外层类型。Targo 使用 Embedding() 函数表达:

typescript
class Reader {
    buf: slice<byte> = slice.of();
    
    Read(p: slice<byte>): [int, error] {
        return [0, null];
    }
}

class Writer {
    Write(p: slice<byte>): [int, error] {
        return [0, null];
    }
}

// 嵌入多个类
class ReadWriter extends Embedding({Reader, Writer}) {
    BufferSize: int = 0;
}

let rw = new ReadWriter();

// 直接访问提升的成员
rw.Read(buf);     // ✅ 从 Reader 提升
rw.Write(buf);    // ✅ 从 Writer 提升
rw.buf;           // ✅ 从 Reader 提升

编译为 Go:

go
type ReadWriter struct {
    Reader
    Writer
    BufferSize int
}

rw := &ReadWriter{}
rw.Read(buf)
rw.Write(buf)
rw.buf

嵌入接口

typescript
interface Closer {
    Close(): error;
}

// 嵌入接口
class WriteCloser extends Embedding({Writer, Closer: {} as Closer}) {
    Name: string = "";
}

编译为 Go:

go
type WriteCloser struct {
    Writer
    Closer
    Name string
}

接口(Interface)

基本定义

接口定义行为契约,只包含方法签名:

typescript
interface Reader extends GoInterface {
    Read(p: slice<byte>): [int, error];
}

interface Writer extends GoInterface {
    Write(p: slice<byte>): [int, error];
}

interface Closer extends GoInterface {
    Close(): error;
}

编译为 Go:

go
type Reader interface {
    Read(p []byte) (int, error)
}

type Writer interface {
    Write(p []byte) (int, error)
}

type Closer interface {
    Close() error
}

GoInterface 标记

所有 Go 接口应该扩展 GoInterface 标记接口,这使类型系统能够防止无效的指针操作。

接口组合

使用 extends 组合多个接口:

typescript
interface ReadWriter extends GoInterface, Reader, Writer {}

interface ReadWriteCloser extends GoInterface, Reader, Writer, Closer {}

编译为 Go:

go
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

隐式实现

Targo 采用 Go 的隐式接口实现:只要类型实现了接口的所有方法,就自动满足该接口。

typescript
interface Stringer extends GoInterface {
    String(): string;
}

class Point {
    X: float64;
    Y: float64;
    
    // 实现 Stringer 接口
    String(): string {
        return fmt.Sprintf("(%v, %v)", this.X, this.Y);
    }
}

// Point 隐式实现了 Stringer
let s: Stringer = { X: 1.0, Y: 2.0 } as Point;

编译为 Go:

go
type Stringer interface {
    String() string
}

type Point struct {
    X float64
    Y float64
}

func (p Point) String() string {
    return fmt.Sprintf("(%v, %v)", p.X, p.Y)
}

var s Stringer = Point{X: 1.0, Y: 2.0}

显式接口检查

使用 implements 关键字进行编译时接口检查:

typescript
interface Reader extends GoInterface {
    Read(p: slice<byte>): [int, error];
}

// 显式声明实现接口(编译时检查)
class FileReader implements Reader {
    fd: int;
    
    Read(p: slice<byte>): [int, error] {
        // 实现...
        return [0, null];
    }
}

编译为 Go:

go
type FileReader struct {
    fd int
}

func (f *FileReader) Read(p []byte) (int, error) {
    return 0, nil
}

// 编译时接口检查
var _ Reader = (*FileReader)(nil)

类型断言

从接口值中提取具体类型:

typescript
interface Stringer extends GoInterface {
    String(): string;
}

let s: Stringer = { X: 1.0, Y: 2.0 } as Point;

// 类型断言(可能 panic)
let p1 = s as Point;

// 安全类型断言(返回元组)
let [p2, ok] = s as? Point;
if (ok) {
    // p2 是 Point 类型
}

编译为 Go:

go
var s Stringer = Point{X: 1.0, Y: 2.0}

p1 := s.(Point)

p2, ok := s.(Point)
if ok {
    // p2 是 Point 类型
}

空接口(any)

空接口可以持有任何类型的值:

typescript
let data: any = "hello";
data = 42;
data = { X: 1.0, Y: 2.0 };

编译为 Go:

go
var data any = "hello"
data = 42
data = Point{X: 1.0, Y: 2.0}

TypeScript 对比

类的差异

特性TypeScriptTargo
默认语义引用引用(映射到 Go 指针)
私有字段private 关键字# 前缀
继承extends不支持(使用 Embedding
构造函数constructorconstructor(编译为工厂函数)
方法接收器无概念指针/值接收器

接口的差异

特性TypeScriptTargo
接口内容方法 + 属性仅方法
实现方式结构化类型隐式实现
运行时编译时擦除运行时类型
类型断言asas / as?

实际示例

HTTP 处理器

typescript
interface Handler extends GoInterface {
    ServeHTTP(w: ResponseWriter, r: Request): void;
}

class FileServer implements Handler {
    Root: string;
    
    constructor(root: string) {
        this.Root = root;
    }
    
    ServeHTTP(w: ResponseWriter, r: Request): void {
        let path = this.Root + r.URL.Path;
        let content = os.ReadFile(path);
        w.Write(content);
    }
}

// 使用
let handler: Handler = new FileServer("/var/www");
http.Handle("/", handler);

日志装饰器

typescript
interface Logger extends GoInterface {
    Log(msg: string): void;
}

class ConsoleLogger implements Logger {
    Log(msg: string): void {
        console.log(msg);
    }
}

// 使用接口嵌入实现装饰器模式
class TimestampLogger extends Embedding({Logger: {} as Logger}) {
    Prefix: string = "";
    
    Log(msg: string): void {
        let timestamp = time.Now().Format(time.RFC3339);
        this.Logger.Log(`[${timestamp}] ${this.Prefix}: ${msg}`);
    }
}

// 使用
let baseLogger: Logger = new ConsoleLogger();
let tsLogger = new TimestampLogger();
tsLogger.Logger = baseLogger;
tsLogger.Prefix = "APP";
tsLogger.Log("Hello");  // [2024-01-01T12:00:00Z] APP: Hello

常见陷阱

1. 忘记 GoInterface 标记

typescript
// ❌ 错误:缺少 GoInterface
interface Reader {
    Read(p: slice<byte>): [int, error];
}

// ✅ 正确
interface Reader extends GoInterface {
    Read(p: slice<byte>): [int, error];
}

2. 接口中定义属性

typescript
// ❌ 错误:接口不能有属性
interface Config extends GoInterface {
    host: string;  // 编译错误
}

// ✅ 正确:使用 class
class Config {
    host: string;
}

3. nil 接口值陷阱

typescript
interface Writer extends GoInterface {
    Write(p: slice<byte>): [int, error];
}

// nil 接口值
let w1: Writer = null;

// 持有 nil 指针的接口值
let fp: File | null = null;
let w2: Writer = fp;  // w2 != null!

if (w1 == null) { /* true */ }
if (w2 == null) { /* false! */ }

最佳实践

  1. 优先使用接口定义行为契约

    • 接口使代码更灵活、可测试
    • 遵循"面向接口编程"原则
  2. 使用 # 前缀保护内部状态

    • 私有字段防止外部直接访问
    • 通过方法提供受控访问
  3. 合理使用嵌入

    • 嵌入实现代码复用
    • 避免过深的嵌入层次
  4. 显式接口检查

    • 使用 implements 确保类型安全
    • 编译时发现接口不匹配
  5. 安全的类型断言

    • 优先使用 as? 而非 as
    • 始终检查断言结果

下一步

参考