Appearance
类与接口
本指南介绍 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: string | Name string | 公开 | 默认大写(Go 公开) |
#secret: string | secret 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 对比
类的差异
| 特性 | TypeScript | Targo |
|---|---|---|
| 默认语义 | 引用 | 引用(映射到 Go 指针) |
| 私有字段 | private 关键字 | # 前缀 |
| 继承 | extends | 不支持(使用 Embedding) |
| 构造函数 | constructor | constructor(编译为工厂函数) |
| 方法接收器 | 无概念 | 指针/值接收器 |
接口的差异
| 特性 | TypeScript | Targo |
|---|---|---|
| 接口内容 | 方法 + 属性 | 仅方法 |
| 实现方式 | 结构化类型 | 隐式实现 |
| 运行时 | 编译时擦除 | 运行时类型 |
| 类型断言 | as | as / 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! */ }最佳实践
优先使用接口定义行为契约
- 接口使代码更灵活、可测试
- 遵循"面向接口编程"原则
使用
#前缀保护内部状态- 私有字段防止外部直接访问
- 通过方法提供受控访问
合理使用嵌入
- 嵌入实现代码复用
- 避免过深的嵌入层次
显式接口检查
- 使用
implements确保类型安全 - 编译时发现接口不匹配
- 使用
安全的类型断言
- 优先使用
as?而非as - 始终检查断言结果
- 优先使用