{"id":14990723,"url":"https://github.com/zhuyadong/zoop","last_synced_at":"2025-07-07T00:31:49.323Z","repository":{"id":252225241,"uuid":"839802886","full_name":"zhuyadong/zoop","owner":"zhuyadong","description":"A Zig OOP solution","archived":false,"fork":false,"pushed_at":"2025-03-08T14:37:26.000Z","size":82,"stargazers_count":26,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T22:22:01.505Z","etag":null,"topics":["class","interface","oop","zig","zig-package"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zhuyadong.png","metadata":{"files":{"readme":"README.CN.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-08T11:00:27.000Z","updated_at":"2025-03-12T07:51:51.000Z","dependencies_parsed_at":"2024-08-26T19:04:25.628Z","dependency_job_id":"d0744c1f-7c9b-4474-8425-6d65efe4a21d","html_url":"https://github.com/zhuyadong/zoop","commit_stats":{"total_commits":73,"total_committers":1,"mean_commits":73.0,"dds":0.0,"last_synced_commit":"e747a43890ff8e486eeb6426bd042859279c95b5"},"previous_names":["zhuyadong/zoop"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhuyadong%2Fzoop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhuyadong%2Fzoop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhuyadong%2Fzoop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zhuyadong%2Fzoop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zhuyadong","download_url":"https://codeload.github.com/zhuyadong/zoop/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248507784,"owners_count":21115674,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["class","interface","oop","zig","zig-package"],"created_at":"2024-09-24T14:20:39.693Z","updated_at":"2025-04-12T02:43:31.846Z","avatar_url":"https://github.com/zhuyadong.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [中文](README.CN.md) | [English](README.md)\n# Zoop 是一种 Zig 的 OOP 方案\n\n## 安装\n在项目根目录下：\n```shell\nzig fetch \"git+https://github.com/zhuyadong/zoop.git\" --save=zoop\n```\n如果要安装特定版本：\n```shell\nzig fetch \"git+https://github.com/zhuyadong/zoop.git#\u003cref id\u003e\" --save=zoop\n```\n\n## 定义类\n```zig\n// 定义一个类 Human\npub const Human = struct {\n    // zoop 类的第一个字段必须对齐为 `zoop.alignment`\n    name: []const u8 align(zoop.alignment),\n    age: u8 = 30,\n\n    // 如果没有清理工作，可以不定义`deinit`\n    pub fn deinit(self: *Human) void {\n        self.name = \"\";\n    }\n\n    pub fn getName(self: *const Human) []const u8 {\n        return self.name;\n    }\n\n    pub fn setName(self: *Human, name: []const u8) void {\n        self.name = name;\n    }\n};\n```\n\n## 创建和销毁类对象\n```zig\nconst t = std.testing;\n\n// 在堆上创建一个 `Human`\nvar phuman = try zoop.new(t.allocator, Human, null);\n// 类字段如果有缺省值，则对象字段就会初始化为缺省值\ntry t.expect(phuman.age == 30);\n// 销毁对象，释放内存。如果类定义了`deinit`，则会先调用`deinit`，然后释放内存。\nzoop.destroy(phuman);\n\n// 在栈上创建一个 `Human`\nvar human = zoop.make(Human, null);\n// 通过`ptr()`访问对象字段\ntry t.expect(human.ptr().age == 30);\n// 清理对象(会调用`deinit`如果有)，如果没有需要清理的工作，也可以不调用 `zoop.destroy`\nzoop.destroy(human.ptr());\n\n// `zoop.new`和`zoop.make`都支持创建时初始化\nphuman = try zoop.new(t.allocator, Human, .{.name = \"HeapObj\", .age = 1});\nhuman = zoop.make(Human, .{.name = \"StackObj\", .age = 2});\ntry t.expect(phuman.age == 1);\ntry t.expect(human.ptr().age == 2);\n```\n关于调用 `deinit`:\n`zoop.destroy`会依次调用类及其所有父类的 `deinit` 方法（如果有）\n\n## 继承\n```zig\n// 定义 `SuperMan`，继承自 `Human`，父类必须是第一个字段且对齐为 `zoop.alignment`，\n// 字段名是随意的，不是必须为`super`，但建议用 `super`\npub const SuperMan = struct {\n    super: Human align(zoop.alignment),\n    // SuperMan 能活很久，u8 无法满足它，我们用 u16\n    age: u16 = 9999,\n\n    pub fn getAge(self: *SuperMan) u16 {\n        return self.age;\n    }\n\n    pub fn setAge(self: *SuperMan, age: u16) void {\n        self.age = age;\n    }\n};\n\n// 先创建一个 `SuperMan` 对象\nvar psuperman = try zoop.new(t.allocator, SuperMan, null);\n// 调用父类方法\npsuperman.super.setName(\"super\");\n// 或者这样调用父类方法，这种方法适合继承层次太深，已经不知道哪个父亲实现了`setName`方法的情况。\n// 另外，既然叫 `upcall`，就表示哪怕`SuperMan`自己实现了`setName`，\n// 下面的调用仍然会调用最近的父类的`setName`方法\nzoop.upcall(psuperman, .setName, .{\"super\"});\n// 也可以灵活的访问类继承树上所有字段，比如想访问 `Human.age` 字段，可以这样：\nvar phuman_age = zoop.getField(psuperman, \"age\", u8);\ntry t.expect(phuman_age.* == 30);\n// 访问 `SuperMan.age`，可以这样：\nvar psuper_age = zoop.getField(psuperman, \"age\", u16);\ntry t.expect(psuper_age.* == 9999);\n// 要注意的是，如果两个`age`都是同样类型，又都叫 \"age\"，\n// 那上面的 `zoop.getField`调用会导致编译错误，以避免 bug\n```\n\n## 类的类型转换\n```zig\n// 先创建一个 Human，一个 SuperMan\nvar phuman = try zoop.new(t.allocator, Human, null);\nvar psuper = try zoop.new(t.allocator, SuperMan, null);\n\n// 子类可以转成父类\ntry t.expect(zoop.as(psuper, Human) != null);\ntry t.expect(zoop.cast(psuper, Human).age == 30);\n// 父类不能转成子类 (如果用 `zoop.cast` 就会编译错)\ntry t.expect(zoop.as(phuman, SuperMan) == null);\n// 指向子类的父类指针可以转换成子类\nphuman = zoop.cast(psuper, Human);\ntry t.expect(zoop.as(phuman, SuperMan) != null);\n```\n\n## 定义接口\n```zig\n// 定义个用来访问名字的接口 `IName`\npub const IName = struct {\n    // 接口只能定义`ptr`和`vptr`两个字段，且名字和类型都必须和下面一样\n    ptr: *anyopaque,\n    vptr: *anyopaque,\n\n    // 定义 `getName` 接口方法\n    pub fn getName(self: IHuman) []const u8 {\n        return zoop.icall(self, .getName, .{});\n    }\n    // 定义 `setName` 接口方法\n    pub fn setName(self: IHuman, name: []const u8) void {\n        zoop.icall(self, .setName, .{name});\n    }\n    // 别管 `zoop.icall` 是什么东西，照着写就行了\n};\n\n// 再定义个用来访问年龄的接口 `IAge`\npub const IAge = struct {\n    ptr: *anyopaque,\n    vptr: *anyopaque,\n\n    pub fn getAge(self: IHuman) u16 {\n        return zoop.icall(self, .getAge, .{});\n    }\n    pub fn setAge(self: IHuman, age: u16) void {\n        zoop.icall(self, .setAge, .{age});\n    }\n}\n\n// 接口也可以继承\npub const INameAndAge struct {\n    pub const extends = .{IName, IAge};\n\n    ptr: *anyopaque,\n    vptr: *anyopaque,\n}\n\n// 可以指定接口中哪些方法不包含到接口 API 中。\n// 只能指定本接口内定义的方法，不会影响继承来的方法\npub const INameAndAge struct {\n    pub const extends = .{IName, IAge};\n    // 不要包含 “eql\" 方法\n    pub const excludes = .{\"eql\"};\n\n    ptr: *anyopaque,\n    vptr: *anyopaque,\n\n    pub fn eql(self: INameAndAge, other: INameAndAge) bool {\n        return self.ptr == other.ptr;\n    }\n}\n\n// 接口还可以提供`api`的缺省实现, 这样申明实现接口的类可以不实现这些接口,\n// 仍然能正确编译和工作 (接口就变成了抽象类)\npub const IName = struct {\n    ...// 和上面一样\n\n    pub fn Default(comptime Class: type) type {\n        return struct {\n            pub fn getName(_: *Class) []const u8 {\n                return \"default name\";\n            }\n        }\n    }\n}\n\n```\n\n## 实现接口\n```zig\n// 我们让 `Human` 实现 `IName` 接口\npub const Human = struct {\n    pub const extends = .{IName};\n    ...//和上面的代码一样\n};\n\n// 让 `SuperMan` 实现 `IAge` 接口\npub const SuperMan = struct {\n    pub const extends = .{IAge};\n    ...//和上面的代码一样\n}\n// 父类实现的接口，了类自动实现，因此 `SuperMan` 也实现了 `IName`，虽然它只申明了实现 `IAge`。\n// 子类可以重复申明实现父类已经实现的接口，这样做不会有什么问题，也不会对结果有什么影响，\n// 比如下面的代码和上面的等价：\npub const SuperMan = struct {\n    pub const extends = .{IAge, IName};\n    ...\n}\n```\n\n## 类和接口之间转换\n```zig\n// 先创建一个 Human，一个 SuperMan\nvar phuman = try zoop.new(t.allocator, Human, .{.name = \"human\"});\nvar psuper = try zoop.new(t.allocator, SuperMan, .{.super = .{.name = \"super\"}});\n\n// Human 实现了 IName，所以可以转\nvar iname = zoop.cast(phuman, IName);\n// SuperMan 实现了 IAge，所以可以转\nvar iage = zoop.cast(psuper, IAge);\ntry t.expect(iage.getAge() == psuper.age);\ntry t.expectEqualStrings(iname.getName(), phuman.name);\n// Human 没有实现 IAge，所以转换会失败。(注意现在`iname`指向的是`phuman`，`iage`指向的是`psuper`)\ntry t.expect(zoop.as(phuman, IAge) == null);\ntry t.expect(zoop.as(iname, IAge) == null);\n// 现在让 iname 指向 psuper\niname = zoop.cast(psuper, IName);\n// 或者这样写也行，就是性能有点影响 (`cast`是O(1)，而`as`最差情况下是O(n) n=SuperMan实现的接口数目)\niname = zoop.as(psuper, IName).?;\n// 现在 iname 就可以转 IAge了\ntry t.expect(zoop.as(iname, IAge) != null);\ntry t.expectEqualStrings(iname.getName(), \"super\");\n// 万物都可转成 zoop.IObject\ntry t.expect(zoop.as(phuman, zoop.IObject) != null);\ntry t.expect(zoop.as(psuper, zoop.IObject) != null);\n// 也可从 IObject 转回来\nvar iobj = zoop.cast(psuper, zoop.IObject);\ntry t.expect(zoop.as(iobj, SuperMan).? == psuper);\n```\n总结一下 `cast` 和 `as`：\n- `cast`适用情况\n  - 子类 -\u003e 父类\n  - 子接口 -\u003e 父接口\n  - 类 -\u003e 类及其父类实现的接口\n- `as`适用的情况\n  - 所有`cast`适用和不适用的情况 (万物都可`as`)\n\n## 方法重写和虚方法调用\n```zig\n// 假如 SuperMan 重写了 getName 方法\npub const SuperMan = struct {\n    ...//和上面一样\n\n    pub fn getName(_: *SuperMan) []const u8 {\n        return \"override\";\n    }\n}\n\n// 现在 IName.getName 将会调用 SuperMan.getName 而不是 Human.getName\nvar psuper = try zoop.new(t.allocator, SuperMan, .{.super = .{.name = \"human\"}});\nvar iname = zoop.cast(psuper, IName);\ntry t.expectEqualStrings(iname.getName(), \"override\");\n// 另一种调用接口方法的风格\ntry t.expectEqualStrings(zoop.vcall(psuper, IName.getName, .{}), \"override\");\n// 虚方法调用对转换后的类同样有用\nvar phuman = zoop.cast(psuper, Human);\niname = zoop.cast(phuman, IName);\ntry t.expectEqualStrings(iname.getName(), \"override\");\ntry t.expectEqualStrings(zoop.vcall(phuman, IName.getName, .{}), \"override\");\n```\n`vcall`的性能说明：\n`vcall`会在能使用`cast`的情况下使用`cast`，否则使用`as`\n\n## 调试用接口 `IObject.formatAny`\n`zoop.IObject`能方便通过 `std.fmt` 的 `format(...)`机制来输出对象的字符串形式内容。\n```zig\n// 定义一个实现 `zoop.IObject.formatAny`的类\npub const SomeClass = struct {\n    name:[]const u8 align(zoop.alignment) = \"some\";\n\n    pub fn formatAny(self: *SomeClass, writer: std.io.AnyWriter) anyerror!void {\n        try writer.print(\"SomeClass.name = {s}\", .{self.name});\n    }\n}\n\n// 下面代码就能输出 `SomeClass.formatAny` 的内容\nconst psome = try zoop.new(t.allocator, SomeClass, null);\nstd.debug.print(\"{}\\n\", .{zoop.cast(psome, zoop.IObject)});\n// output: SomeClass.name = some\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhuyadong%2Fzoop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhuyadong%2Fzoop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhuyadong%2Fzoop/lists"}