强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 + overrideabstract + 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 常见陷阱

  1. 必须继承 GObject:Vala 中的类几乎都应该继承自 GLib.Object,否则无法使用信号、属性等 GObject 特性
  2. 构造器使用 Object():不要用 C++ 风格的初始化列表,Vala 使用 Object() 链式调用
  3. 接口只能继承接口:接口不能继承类,但可以继承其他接口
  4. 一个类可以实现多个接口:使用逗号分隔
  5. 属性变化通知:使用 notify_property("name") 手动触发,或让编译器自动生成
  6. 析构器:Vala 使用引用计数,通常不需要手动析构

4.10 扩展阅读

资源链接
Vala 类参考https://wiki.gnome.org/Projects/Vala/Classes
Vala 接口参考https://wiki.gnome.org/Projects/Vala/Interfaces
GObject 属性系统https://docs.gtk.org/gobject/properties.html
GObject 信号系统https://docs.gtk.org/gobject/signals.html
设计模式https://wiki.gnome.org/Projects/Vala/DesignPatterns

4.11 总结

要点说明
继承自 GLib.Object,支持属性、信号
属性{ get; set; } 语法,自动生成 GObject 属性
构造器使用 Object() 链式调用
继承单继承,使用 virtual/override 实现多态
接口多实现,支持默认方法、属性、信号
抽象类使用 abstract 关键字,不能实例化
信号观察者模式,使用 connect/disconnect

下一章我们将深入了解 GObject 类型系统。→ 第 5 章:GObject 类型系统