委托模式:把活儿派出去的艺术

委托模式,说白了就是「我不干,你干」。

我刚开始接触这个模式时,觉得它太简单了——不就是A调用B的方法吗?后来在项目中踩过几次坑,才真正理解它的价值。你想想看,如果所有事情都自己扛,类会变得臃肿不堪。委托模式就是帮你把职责分出去,让代码更灵活。

什么是委托模式?

委托模式是一种行为设计模式。它让一个对象(委托者)把某些任务交给另一个对象(被委托者)去完成。委托者持有被委托者的引用,调用它的方法,就像自己亲自做了一样。

嗯,这里要注意:委托和继承不同。继承是「我是」,委托是「我有」。我习惯用委托代替继承,因为组合比继承更灵活。

核心三要素:

  • 委托接口:定义双方约定的契约
  • 委托者:持有委托接口引用,调用方法
  • 被委托者:实现委托接口,真正干活

一个简单的例子

假设你是一个项目经理,手头有个需求要开发。你不可能自己写代码,于是你把任务委托给程序员。项目经理就是委托者,程序员就是被委托者。

先看Java版本:

// 委托接口
interface Task {
    void execute();
}

// 被委托者:程序员
class Programmer implements Task {
    @Override
    public void execute() {
        System.out.println("程序员正在写代码...");
    }
}

// 委托者:项目经理
class ProjectManager {
    private Task task;
    
    public ProjectManager(Task task) {
        this.task = task;
    }
    
    public void assignTask() {
        System.out.println("项目经理分配任务");
        task.execute();  // 委托给程序员
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        ProjectManager pm = new ProjectManager(programmer);
        pm.assignTask();
    }
}

C++版本也差不多:

#include <iostream>
#include <memory>

// 委托接口
class Task {
public:
    virtual void execute() = 0;
    virtual ~Task() = default;
};

// 被委托者
class Programmer : public Task {
public:
    void execute() override {
        std::cout << "程序员正在写代码..." << std::endl;
    }
};

// 委托者
class ProjectManager {
private:
    std::shared_ptr<Task> task;
public:
    ProjectManager(std::shared_ptr<Task> t) : task(t) {}
    
    void assignTask() {
        std::cout << "项目经理分配任务" << std::endl;
        task->execute();
    }
};

int main() {
    auto programmer = std::make_shared<Programmer>();
    ProjectManager pm(programmer);
    pm.assignTask();
    return 0;
}

事件处理与回调机制

委托模式最常见的应用场景就是事件处理和回调。我在项目中遇到过很多次:UI按钮点击、网络请求完成、定时器触发……这些本质上都是委托。

Java里的事件监听器就是典型的委托模式。你注册一个监听器(被委托者),事件源(委托者)在事件发生时调用你的方法。

// 事件监听接口
interface OnClickListener {
    void onClick(View view);
}

// 按钮(委托者)
class Button {
    private OnClickListener listener;
    
    public void setOnClickListener(OnClickListener listener) {
        this.listener = listener;
    }
    
    public void click() {
        if (listener != null) {
            listener.onClick(this);
        }
    }
}

// 使用
public class App {
    public static void main(String[] args) {
        Button button = new Button();
        button.setOnClickListener(view -> {
            System.out.println("按钮被点击了!");
        });
        button.click();
    }
}

C++里可以用函数指针或std::function实现回调:

#include <iostream>
#include <functional>

class Button {
private:
    std::function<void()> callback;
public:
    void setOnClick(std::function<void()> cb) {
        callback = cb;
    }
    
    void click() {
        if (callback) {
            callback();
        }
    }
};

int main() {
    Button btn;
    btn.setOnClick([]() {
        std::cout << "按钮被点击了!" << std::endl;
    });
    btn.click();
    return 0;
}

我的习惯:在Java中,我倾向于用接口定义回调;在C++中,我更喜欢用std::function,因为它更灵活,可以绑定lambda、函数指针、成员函数等。

委托模式的核心逻辑

下面这张图展示了委托模式的整体结构:

委托接口 «interface» 委托者 持有委托接口引用 调用接口方法 被委托者 实现委托接口 真正执行任务 实现 委托模式核心结构 委托者通过接口将任务委托给被委托者

委托 vs 继承

我经常被问到:什么时候用委托,什么时候用继承?

对比维度 委托 继承
关系 「有」的关系(has-a) 「是」的关系(is-a)
耦合度 低,通过接口解耦 高,子类依赖父类
灵活性 运行时可以替换被委托者 编译时确定,不易改变
代码复用 通过组合复用 通过继承复用
适用场景 行为需要动态变化 行为稳定,层次明确

避坑指南:我曾经在一个项目中过度使用继承,导致类层次深达6层,改一个父类方法,所有子类都要跟着改。后来重构时全部改成委托,代码瞬间清爽了。记住:能用组合就别用继承。

实际项目中的应用

我在做Android开发时,经常用委托模式处理复杂的UI交互。比如一个列表项有多种点击行为:点赞、评论、分享。如果每个行为都写在Activity里,代码会变得又臭又长。

我的做法是:

  1. 定义一个点击事件接口,包含各种行为方法
  2. 创建不同的被委托者实现这个接口(点赞处理器、评论处理器等)
  3. 在列表适配器里持有委托接口,根据位置动态切换被委托者

这样,新增一种点击行为只需要新增一个被委托者类,完全不用改现有代码。这就是开闭原则的体现。

委托模式的优缺点

优点:

  • 降低耦合度,委托者和被委托者通过接口交互
  • 提高灵活性,运行时可以动态替换被委托者
  • 符合单一职责原则,每个类只做一件事
  • 便于单元测试,可以轻松mock被委托者

缺点:

  • 增加类的数量,需要额外定义接口和实现类
  • 如果委托链过长,调试起来会比较麻烦
  • 性能上有微小开销(方法调用多了一层)

我的建议:不要为了用委托而用委托。如果一个类只有一两个方法需要委托,直接写个回调函数就够了。当委托逻辑变得复杂时,再考虑提取成接口。

总结

委托模式不是什么高深的技术,它就是「把活儿派出去」的编程思想。你想想看,现实中的项目经理不也是这样吗?他们不会自己写代码,而是把任务分配给合适的人。

在代码里,委托模式让我们的类更专注、更灵活。事件监听、回调函数、策略模式……这些本质上都是委托的变体。掌握了委托模式,你就掌握了软件设计中「分而治之」的精髓。

嗯,最后说一句:写代码就像带团队,懂得委托的人,才能写出好代码。


公众号:蓝海资料掘金营,微信deep3321