Vala 语言入门教程 / 04 - 面向对象编程
第 4 章:面向对象编程
Vala 是一门完全面向对象的语言。本章将深入讲解 Vala 的 OOP 特性,这些特性与 GObject 类型系统紧密相连。
4.1 类的基本定义
4.1.1 最简单的类
// 定义一个类(必须继承自某个 GObject 类型)
public class Animal : Object {
// 成员变量(属性)
public string name;
public int age;
// 方法
public void speak () {
print ("%s 说: 汪汪!(%d 岁)\n", name, age);
}
}
void main () {
// 创建对象(使用 new 关键字)
var dog = new Animal ();
dog.name = "旺财";
dog.age = 3;
dog.speak ();
}
💡 在 Vala 中,几乎所有类都继承自
GLib.Object(即 GObject)。这是 Vala 与 GObject 类型系统绑定的基础。
4.1.2 类的访问修饰符
| 修饰符 | 说明 | C 代码可见性 |
|---|---|---|
public | 公开,任何地方可访问 | 完全导出 |
private | 私有,仅类内部可访问 | static 函数,不导出 |
protected | 受保护,类及其子类可访问 | 子类可见 |
internal | 包内可见(默认) | 不导出到公共头文件 |
public class BankAccount : Object {
// 公开属性
public string owner { get; set; }
// 私有属性
private double balance = 0.0;
// 受保护方法
protected void log_transaction (string type, double amount) {
print ("[%s] %s: %.2f\n", type, owner, amount);
}
// 公开方法
public void deposit (double amount) {
if (amount <= 0) {
print ("存款金额必须为正数\n");
return;
}
balance += amount;
log_transaction ("存款", amount);
}
public bool withdraw (double amount) {
if (amount > balance) {
print ("余额不足\n");
return false;
}
balance -= amount;
log_transaction ("取款", amount);
return true;
}
public double get_balance () {
return balance;
}
}
void main () {
var account = new BankAccount ();
account.owner = "张三";
account.deposit (1000);
account.withdraw (300);
print ("余额: %.2f\n", account.get_balance ());
}
4.2 属性(Properties)
属性是 Vala 的核心特性之一,它在底层自动生成 GObject 的属性注册代码。
4.2.1 基本属性
public class Person : Object {
// 自动属性(编译器自动生成 getter/setter)
public string name { get; set; }
public int age { get; set; default = 0; }
public bool active { get; set; default = true; }
}
void main () {
var p = new Person ();
p.name = "张三";
p.age = 30;
print ("姓名: %s, 年龄: %d\n", p.name, p.age);
}
4.2.2 只读属性
public class Circle : Object {
public double radius { get; construct; }
// 只读属性(只有 getter)
public double area {
get {
return 3.14159 * radius * radius;
}
}
public double circumference {
get {
return 2 * 3.14159 * radius;
}
}
public Circle (double radius) {
Object (radius: radius);
}
}
void main () {
var c = new Circle (5.0);
print ("半径: %.1f\n", c.radius);
print ("面积: %.2f\n", c.area);
print ("周长: %.2f\n", c.circumference);
// c.area = 100; // 编译错误:area 是只读的
}
4.2.3 自定义 getter/setter
public class Temperature : Object {
private double _celsius;
// 自定义 getter/setter
public double celsius {
get { return _celsius; }
set { _celsius = value; }
}
// 以华氏度形式访问
public double fahrenheit {
get { return _celsius * 9.0 / 5.0 + 32; }
set { _celsius = (value - 32) * 5.0 / 9.0; }
}
// 只读属性
public string description {
get {
if (_celsius < 0) return "寒冷";
if (_celsius < 15) return "凉爽";
if (_celsius < 25) return "温暖";
if (_celsius < 35) return "炎热";
return "酷热";
}
}
}
void main () {
var temp = new Temperature ();
temp.celsius = 25;
print ("摄氏: %.1f°C\n", temp.celsius);
print ("华氏: %.1f°F\n", temp.fahrenheit);
print ("描述: %s\n", temp.description);
temp.fahrenheit = 100;
print ("\n设置华氏 100°F:\n");
print ("摄氏: %.1f°C\n", temp.celsius);
print ("描述: %s\n", temp.description);
}
4.2.4 属性规范总览
| 属性声明 | 含义 |
|---|---|
public string name { get; set; } | 读写属性 |
public string name { get; } | 只读(只能在构造器中设置) |
public string name { get; private set; } | 外部只读,内部可写 |
public string name { get; construct; } | 构造时设置,之后只读 |
public string name { get; set; default = "x"; } | 带默认值 |
public string name { owned get; set; } | getter 返回所有权(引用计数 +1) |
4.3 构造器(Constructors)
4.3.1 基本构造器
public class User : Object {
public string username { get; set; }
public string email { get; set; }
public int login_count { get; private set; default = 0; }
// 主构造器(链式调用 Object() 设置属性)
public User (string username, string email) {
Object (
username: username,
email: email
);
}
// 链式构造器(Chain-up)
public User.with_defaults (string username) {
Object (
username: username,
email: username + "@example.com"
);
}
// 构造器后初始化(construct 块)
construct {
print ("用户 %s 已创建\n", username);
}
public void login () {
login_count++;
print ("%s 登录(第 %d 次)\n", username, login_count);
}
}
void main () {
var user1 = new User ("alice", "alice@example.com");
user1.login ();
var user2 = new User.with_defaults ("bob");
print ("Bob 的邮箱: %s\n", user2.email);
}
4.3.2 构造器链
public class Config : Object {
public string host { get; set; }
public int port { get; set; }
public bool use_ssl { get; set; }
// 完整参数构造器
public Config (string host, int port, bool use_ssl) {
Object (host: host, port: port, use_ssl: use_ssl);
}
// 常用默认配置
public Config.localhost () {
Object (host: "127.0.0.1", port: 8080, use_ssl: false);
}
public Config.production (string host) {
Object (host: host, port: 443, use_ssl: true);
}
public string to_uri () {
string scheme = use_ssl ? "https" : "http";
return "%s://%s:%d".printf (scheme, host, port);
}
}
void main () {
var c1 = new Config ("example.com", 80, false);
var c2 = new Config.localhost ();
var c3 = new Config.production ("api.example.com");
print ("c1: %s\n", c1.to_uri ());
print ("c2: %s\n", c2.to_uri ());
print ("c3: %s\n", c3.to_uri ());
}
4.4 继承(Inheritance)
4.4.1 基本继承
// 基类
public class Shape : Object {
public string color { get; set; }
public Shape (string color) {
Object (color: color);
}
// 虚方法(子类可重写)
public virtual double area () {
return 0.0;
}
public virtual string describe () {
return "形状[%s]".printf (color);
}
}
// 子类:矩形
public class Rectangle : Shape {
public double width { get; set; }
public double height { get; set; }
public Rectangle (string color, double width, double height) {
Object (color: color, width: width, height: height);
}
// 重写虚方法
public override double area () {
return width * height;
}
public override string describe () {
return "矩形[%s] %.1fx%.1f".printf (color, width, height);
}
}
// 子类:圆形
public class Circle : Shape {
public double radius { get; set; }
public Circle (string color, double radius) {
Object (color: color, radius: radius);
}
public override double area () {
return 3.14159 * radius * radius;
}
public override string describe () {
return "圆形[%s] r=%.1f".printf (color, radius);
}
}
void main () {
// 多态
Shape[] shapes = {
new Rectangle ("红色", 10, 5),
new Circle ("蓝色", 3),
new Rectangle ("绿色", 8, 4)
};
foreach (var shape in shapes) {
print ("%s -> 面积: %.2f\n", shape.describe (), shape.area ());
}
}
4.4.2 抽象类和抽象方法
// 抽象类(不能实例化)
public abstract class Database : Object {
public string connection_string { get; construct; }
protected Database (string connection_string) {
Object (connection_string: connection_string);
}
// 抽象方法(子类必须实现)
public abstract void connect () throws DBError;
public abstract void disconnect ();
public abstract GLib.List<string> query (string sql);
// 普通方法(子类可以直接使用)
public void print_info () {
print ("数据库连接: %s\n", connection_string);
}
}
// 错误域
errordomain DBError {
CONNECTION_FAILED,
QUERY_ERROR
}
// 具体实现:SQLite
public class SQLiteDatabase : Database {
private bool connected = false;
public SQLiteDatabase (string path) {
Object (connection_string: path);
}
public override void connect () throws DBError {
print ("连接到 SQLite: %s\n", connection_string);
connected = true;
}
public override void disconnect () {
print ("断开 SQLite 连接\n");
connected = false;
}
public override GLib.List<string> query (string sql) {
var results = new GLib.List<string> ();
if (connected) {
results.append ("结果1");
results.append ("结果2");
}
return results;
}
}
void main () {
// Database db = new Database (); // 编译错误:抽象类不能实例化
Database db = new SQLiteDatabase ("/tmp/test.db");
db.print_info ();
try {
db.connect ();
var results = db.query ("SELECT * FROM users");
foreach (var row in results) {
print (" %s\n", row);
}
db.disconnect ();
} catch (DBError e) {
printerr ("数据库错误: %s\n", e.message);
}
}
4.4.3 虚方法 vs 抽象方法
| 特性 | 虚方法 (virtual) | 抽象方法 (abstract) |
|---|---|---|
| 是否有实现 | 有默认实现 | 没有实现 |
| 子类是否必须重写 | 否(可选) | 是(必须) |
| 所在类 | 非抽象类或抽象类 | 只能在抽象类中 |
| 关键字 | virtual + override | abstract + override |
4.4.4 new 关键字隐藏基类方法
public class Parent : Object {
public void greet () {
print ("来自 Parent\n");
}
}
public class Child : Parent {
// 隐藏基类方法(不推荐使用,会收到编译警告)
public new void greet () {
print ("来自 Child\n");
}
}
void main () {
Parent p = new Child ();
p.greet (); // 输出 "来自 Parent"(静态分派)
Child c = new Child ();
c.greet (); // 输出 "来自 Child"
}
⚠️ 注意:使用
new隐藏方法会导致静态分派,而非多态。推荐使用virtual/override实现多态。
4.5 接口(Interfaces)
4.5.1 定义和实现接口
// 定义接口
public interface Printable : Object {
// 接口方法(实现类必须实现)
public abstract string to_string ();
// 接口的默认方法(实现类可以直接使用)
public void print_info () {
print ("[Printable] %s\n", to_string ());
}
}
// 定义另一个接口
public interface Serializable : Object {
public abstract string serialize ();
public abstract bool deserialize (string data);
}
// 实现多个接口
public class Document : Object, Printable, Serializable {
public string title { get; set; }
public string content { get; set; }
public Document (string title, string content) {
Object (title: title, content: content);
}
// 实现 Printable 接口
public string to_string () {
return "文档: %s (%d 字)".printf (title, content.length);
}
// 实现 Serializable 接口
public string serialize () {
return "%s|%s".printf (title, content);
}
public bool deserialize (string data) {
string[] parts = data.split ("|");
if (parts.length >= 2) {
title = parts[0];
content = parts[1];
return true;
}
return false;
}
}
void main () {
var doc = new Document ("报告", "这是一份测试报告");
// 作为 Printable 使用
Printable p = doc;
p.print_info ();
// 作为 Serializable 使用
Serializable s = doc;
string data = s.serialize ();
print ("序列化: %s\n", data);
var doc2 = new Document ("", "");
doc2.deserialize (data);
print ("反序列化: %s\n", doc2.to_string ());
}
4.5.2 接口与多态
public interface Drawable : Object {
public abstract void draw ();
public abstract int get_width ();
public abstract int get_height ();
}
public class Widget : Object, Drawable {
public string name { get; set; }
private int w, h;
public Widget (string name, int width, int height) {
Object (name: name);
this.w = width;
this.h = height;
}
public void draw () {
print ("绘制 %s (%dx%d)\n", name, w, h);
}
public int get_width () { return w; }
public int get_height () { return h; }
}
public class Canvas : Object, Drawable {
private GLib.List<Drawable> children = new GLib.List<Drawable> ();
public void add (Drawable item) {
children.append (item);
}
public void draw () {
print ("--- 画布开始绘制 ---\n");
foreach (var child in children) {
child.draw ();
}
print ("--- 画布绘制完成 ---\n");
}
public int get_width () { return 800; }
public int get_height () { return 600; }
}
void main () {
var canvas = new Canvas ();
canvas.add (new Widget ("按钮", 100, 30));
canvas.add (new Widget ("文本框", 200, 25));
canvas.add (new Widget ("图片", 300, 200));
canvas.draw ();
}
4.5.3 接口中的属性和信号
public interface Cacheable : Object {
// 接口属性
public abstract bool is_cached { get; set; }
public abstract int64 cache_time { get; set; }
// 接口信号
public signal void cache_expired ();
// 接口方法
public void invalidate_cache () {
is_cached = false;
cache_expired ();
}
}
public class ImageData : Object, Cacheable {
public string path { get; set; }
public bool is_cached { get; set; }
public int64 cache_time { get; set; }
public ImageData (string path) {
Object (path: path, is_cached: false);
}
}
void main () {
var img = new ImageData ("/tmp/photo.jpg");
img.cache_expired.connect (() => {
print ("缓存已过期!\n");
});
img.is_cached = true;
img.invalidate_cache ();
}
4.6 信号(Signals)
信号是 GObject 的核心特性之一,用于实现观察者模式。
4.6.1 定义和使用信号
public class EventEmitter : Object {
// 定义信号
public signal void started ();
public signal void data_received (string data);
public signal int process (int input); // 带返回值的信号
public void run () {
print ("启动...\n");
started ();
print ("处理数据...\n");
int result = process (42);
print ("处理结果: %d\n", result);
data_received ("Hello");
data_received ("World");
}
}
void main () {
var emitter = new EventEmitter ();
// 连接信号处理器
emitter.started.connect (() => {
print (" [处理器] 已启动!\n");
});
emitter.data_received.connect ((data) => {
print (" [处理器] 收到数据: %s\n", data);
});
// 使用返回值的信号
emitter.process.connect ((input) => {
return input * 2; // 返回处理结果
});
emitter.run ();
}
4.6.2 信号连接选项
public class Button : Object {
public string label { get; set; }
public signal void clicked ();
public Button (string label) {
Object (label: label);
}
public void simulate_click () {
clicked ();
}
}
void main () {
var btn = new Button ("确定");
// 1. 普通连接
btn.clicked.connect (() => {
print ("按钮被点击\n");
});
// 2. 连接后自动断开(AFTER 模式)
int click_count = 0;
btn.clicked.connect (() => {
click_count++;
print ("点击次数: %d\n", click_count);
});
// 3. 使用 disconnect 断开连接
GLib.SignalHandler handler_id = 0;
handler_id = btn.clicked.connect (() => {
print ("这个处理器会被断开\n");
});
btn.simulate_click ();
// 断开特定处理器
btn.clicked.disconnect (handler_id);
print ("\n断开后:\n");
btn.simulate_click ();
}
4.6.3 信号与属性变化
public class Settings : Object {
private string _username = "";
public string username {
get { return _username; }
set {
if (_username != value) {
_username = value;
// 手动触发变化通知
notify_property ("username");
}
}
}
// 内置信号:属性变化通知
// 当任何属性变化时,会触发 "notify" 信号
}
void main () {
var settings = new Settings ();
// 监听属性变化
settings.notify["username"].connect ((obj, pspec) => {
print ("用户名已变为: %s\n", settings.username);
});
// 监听所有属性变化
settings.notify.connect ((obj, pspec) => {
print ("属性 '%s' 已变化\n", pspec.name);
});
settings.username = "alice";
settings.username = "bob";
}
4.7 综合示例:事件系统
// 事件类型
public enum EventType {
MOUSE_CLICK,
KEY_PRESS,
WINDOW_RESIZE
}
// 事件数据
public class Event : Object {
public EventType event_type { get; construct; }
public string data { get; construct; }
public Event (EventType type, string data) {
Object (event_type: type, data: data);
}
}
// 事件监听器接口
public interface EventListener : Object {
public abstract void on_event (Event event);
}
// 事件管理器
public class EventManager : Object {
private GLib.HashTable<EventType, GLib.List<EventListener>> listeners;
construct {
listeners = new GLib.HashTable<EventType, GLib.List<EventListener>> (
// 使用直接哈希函数
);
}
public void subscribe (EventType type, EventListener listener) {
if (!listeners.contains (type)) {
listeners[type] = new GLib.List<EventListener> ();
}
listeners[type].append (listener);
}
public void emit (Event event) {
if (listeners.contains (event.event_type)) {
foreach (var listener in listeners[event.event_type]) {
listener.on_event (event);
}
}
}
}
// 具体监听器
public class MouseHandler : Object, EventListener {
public void on_event (Event event) {
if (event.event_type == EventType.MOUSE_CLICK) {
print ("[鼠标] 处理点击: %s\n", event.data);
}
}
}
public class KeyHandler : Object, EventListener {
public void on_event (Event event) {
if (event.event_type == EventType.KEY_PRESS) {
print ("[键盘] 处理按键: %s\n", event.data);
}
}
}
void main () {
var manager = new EventManager ();
var mouse = new MouseHandler ();
var key = new KeyHandler ();
manager.subscribe (EventType.MOUSE_CLICK, mouse);
manager.subscribe (EventType.KEY_PRESS, key);
manager.emit (new Event (EventType.MOUSE_CLICK, "100,200"));
manager.emit (new Event (EventType.KEY_PRESS, "Enter"));
manager.emit (new Event (EventType.WINDOW_RESIZE, "1024x768"));
}
4.8 业务场景:插件系统
// 插件接口
public interface Plugin : Object {
public abstract string name { get; }
public abstract string version { get; }
public abstract void activate ();
public abstract void deactivate ();
}
// 插件管理器
public class PluginManager : Object {
private GLib.List<Plugin> plugins = new GLib.List<Plugin> ();
public signal void plugin_loaded (Plugin plugin);
public signal void plugin_unloaded (Plugin plugin);
public void register (Plugin plugin) {
plugins.append (plugin);
plugin.activate ();
plugin_loaded (plugin);
}
public void unregister (Plugin plugin) {
plugin.deactivate ();
plugins.remove (plugin);
plugin_unloaded (plugin);
}
public void list_plugins () {
print ("已注册插件:\n");
foreach (var plugin in plugins) {
print (" - %s v%s\n", plugin.name, plugin.version);
}
}
}
// 具体插件
public class LoggingPlugin : Object, Plugin {
public string name { get { return "日志插件"; } }
public string version { get { return "1.0.0"; } }
public void activate () {
print ("[%s] 已激活\n", name);
}
public void deactivate () {
print ("[%s] 已停用\n", name);
}
}
public class SecurityPlugin : Object, Plugin {
public string name { get { return "安全插件"; } }
public string version { get { return "2.1.0"; } }
public void activate () {
print ("[%s] 已激活\n", name);
}
public void deactivate () {
print ("[%s] 已停用\n", name);
}
}
void main () {
var manager = new PluginManager ();
manager.plugin_loaded.connect ((plugin) => {
print ("→ 插件加载: %s\n", plugin.name);
});
manager.register (new LoggingPlugin ());
manager.register (new SecurityPlugin ());
print ("\n");
manager.list_plugins ();
}
4.9 注意事项
⚠️ OOP 常见陷阱
- 必须继承 GObject:Vala 中的类几乎都应该继承自
GLib.Object,否则无法使用信号、属性等 GObject 特性 - 构造器使用
Object():不要用 C++ 风格的初始化列表,Vala 使用Object()链式调用 - 接口只能继承接口:接口不能继承类,但可以继承其他接口
- 一个类可以实现多个接口:使用逗号分隔
- 属性变化通知:使用
notify_property("name")手动触发,或让编译器自动生成 - 析构器:Vala 使用引用计数,通常不需要手动析构
4.10 扩展阅读
4.11 总结
| 要点 | 说明 |
|---|---|
| 类 | 继承自 GLib.Object,支持属性、信号 |
| 属性 | { get; set; } 语法,自动生成 GObject 属性 |
| 构造器 | 使用 Object() 链式调用 |
| 继承 | 单继承,使用 virtual/override 实现多态 |
| 接口 | 多实现,支持默认方法、属性、信号 |
| 抽象类 | 使用 abstract 关键字,不能实例化 |
| 信号 | 观察者模式,使用 connect/disconnect |
下一章我们将深入了解 GObject 类型系统。→ 第 5 章:GObject 类型系统