访问者模式:可以在不改变类的前提下给它加功能?访问者模式太神奇了!
访问者模式:优雅地分离数据结构与操作
咱们今天聊聊软件设计里一个经典难题:当你的业务对象(或者叫“数据结构”)已经稳定成型,但后续需要不断为它添加五花八门的新操作时,该怎么办?一种笨方法就是,每来一个新需求,就吭哧吭哧地去修改那些“祖宗级”的类。但这么干,麻烦可就大了。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
访问者模式,就是为了优雅地解决这个问题而生的。它的核心思想,就是把“数据结构”(比如你的商品)和“操作”(比如生成各种格式的报表)彻底分开。这样一来,你想增加新操作时,完全不用碰原来的数据结构,只需要专心写一个新的“访问者”就行,然后让数据结构去“接受”这个访问者来拜访。
从一个电商系统的例子说起
假设你正在开发一个电商系统,里面有几种不同类型的商品:
- 书籍 (Book):有书名、页数。
- 水果 (Fruit):有名称、重量。
- 电子产品 (Electronic):有型号、保修期。
第一个需求来了:给所有商品生成一个展示用的HTML详情页。这简单,你在每个商品类里都加一个toHtml()方法就搞定了。
下个月,产品经理说还需要能导出XML格式的报表。好吧,你搓搓手,又得去每个类里加一个toXml()方法。
再下个月,前端说要JSON格式的数据接口……得,你又要去改那三个类,周而复始。

问题一下子就暴露了:
- 违反开闭原则:每次加一种新格式,都不得不打开并修改所有现有的商品类,这无异于在稳定的核心代码上反复动手术。
- 职责混乱:商品类的本职应该是管理自己的核心数据(比如书名、重量),现在却被迫掺和进了数据展示和格式转换的活儿,变得臃肿不堪。
这时候,访问者模式就该出场了。它告诉你:别折腾那些商品类了。你需要什么新操作(比如导出XML),就专门写一个“XML访问者”,然后让商品们“接待”一下这位访问者,任务就完成了。
一、PHP 8.1+ 实战演示
1. 元素接口 (Element)
首先,我们得定个规矩。所有商品都必须实现这个统一的“元素”接口。它的核心就是一个accept方法,用来迎接访问者。
2. 具体元素 (Concrete Element)
接下来,看看具体的商品类怎么实现。这里用到了一个叫双重分派的精妙技巧。注意看$visitor->visitBook($this)这行:
- 第一重分派:客户端调用
$product->accept($visitor),把访问者“请进门”。 - 第二重分派:商品对象(比如
Book)自己很清楚“我是谁”,所以它立刻调用了访问者身上专门为自己准备的方法——visitBook($this),并把自身引用传递过去。
class Book implements Product
{
public function __construct(
public string $title,
public int $pages
) {}
public function accept(Visitor $visitor): void
{
// 我知道我是 Book,所以我调用 visitBook
$visitor->visitBook($this);
}
}
class Fruit implements Product
{
public function __construct(
public string $name,
public float $weight
) {}
public function accept(Visitor $visitor): void
{
// 我知道我是 Fruit,所以我调用 visitFruit
$visitor->visitFruit($this);
}
}
3. 访问者接口 (Visitor)
访问者接口定义了它能为哪些类型的“客人”(元素)提供服务。每种元素都有一个对应的访问方法。
interface Visitor
{
public function visitBook(Book $book): void;
public function visitFruit(Fruit $fruit): void;
}
4. 具体访问者 (Concrete Visitor)
重头戏在这里。所有具体的操作逻辑,都被封装在各个访问者类中。想加新功能?直接新建一个访问者类就行,原有代码纹丝不动。
// 导出 XML 的逻辑
class XmlExportVisitor implements Visitor
{
public function visitBook(Book $book): void
{
echo "{$book->title} {$book->pages}
\n";
}
public function visitFruit(Fruit $fruit): void
{
echo "{$fruit->name} {$fruit->weight} \n";
}
}
// 导出 JSON 的逻辑(想加新功能?加个新类就行!)
class JsonExportVisitor implements Visitor
{
public function visitBook(Book $book): void
{
echo json_encode(['type' => 'book', 'title' => $book->title]) . "\n";
}
public function visitFruit(Fruit $fruit): void
{
echo json_encode(['type' => 'fruit', 'name' => $fruit->name]) . "\n";
}
}
5. 客户端调用
最后,看看客户端如何使用这套机制,优雅地完成不同格式的导出任务。
$products = [
new Book("PHP 核心技术", 500),
new Fruit("苹果", 1.5),
new Book("设计模式", 300)
];
// 1. 导出 XML
echo "--- Exporting XML ---\n";
$xmlVisitor = new XmlExportVisitor();
foreach ($products as $product) {
$product->accept($xmlVisitor);
}
// 2. 导出 JSON
echo "\n--- Exporting JSON ---\n";
$jsonVisitor = new JsonExportVisitor();
foreach ($products as $product) {
$product->accept($jsonVisitor);
}
// 输出示例:
// --- Exporting XML ---
// PHP 核心技术 ...
// 苹果 ...
// ...
// --- Exporting JSON ---
// {"type":"book","title":"PHP 核心技术"}
// {"type":"fruit","name":"苹果"}
// ...
二、一个至关重要的细节与取舍
访问者模式并非万能灵药,它有一个非常显著的特点(或者说缺点):增加新的元素类型会非常困难。
想象一下,如果你的系统突然要加入一个新的Electronic(电子产品)类。那么,你不仅要修改Visitor接口(增加一个visitElectronic方法),还必须回过头去修改所有已经写好的具体访问者类(比如XmlExportVisitor、JsonExportVisitor),让它们实现对这个新产品的处理逻辑。
所以,这就引出了访问者模式的经典适用场景:数据结构非常稳定,但操作(算法)需要频繁扩展和变化。编译器就是个绝佳的例子——编程语言的语法规则(数据结构)几乎不变,但针对语法树的分析、优化、转换等操作(访问者)却层出不穷。报表系统也是类似,数据模型固定,但导出格式、统计规则常变。
三、什么时候该考虑用它?
总结一下,当你在项目中遇到以下几种情况时,访问者模式值得放入备选方案:
- 你的对象结构由许多不同接口的类对象组成,而你想要执行一些依赖于这些对象具体类型的操作。
- 你需要在对象结构上执行许多彼此无关的不同操作,并且不希望这些操作把对象本身的类弄得乱七八糟。
- 对象结构本身(类的种类)很少改变,但你却需要经常在这个结构上定义新的操作。
四、总结与延伸思考
访问者模式,用一句话概括就是:封装一些作用于某种数据结构中各元素的操作,它可以在不改变该数据结构的前提下,定义作用于这些元素的新操作。
它的核心价值在于扩展操作。通过将数据结构与数据操作解耦,增加新操作变得像搭积木一样简单——新建一个访问者类即可。
最后,留一个有趣的思考题:PHP本身并不支持基于参数类型的真正方法重载。如果PHP支持重载,我们或许就不需要用visitBook、visitFruit这样区分方法名,而可以统一用visit(Book $b)和visit(Fruit $f)。如果真是这样,你觉得accept方法里的双重分派逻辑会发生什么变化?这背后又涉及到静态分派与动态分派哪些微妙的区别呢?
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
蔚来ET5:30万级智能电动轿跑,设计、性能与科技全面进阶
蔚来ET5:30万级智能轿跑的“六边形战士” 在30万元这个竞争白热化的智能电动轿车市场,一款车要想站稳脚跟,必须是个“全能选手”。蔚来ET5,正是这样一款产品。它以卓越的性能、出众的设计和前沿的科技作为核心武器,精准地切入市场,试图重新定义这个级别的价值标杆。 市场定位与外观设计:一眼可辨的先锋姿
苹果正测试四款非AR智能眼镜,含“库克同款”,定位iPhone超级配件
苹果智能眼镜新动向:四款镜框设计曝光,瞄准后发制人 彭博社的科技记者马克·古尔曼最近带来一则消息,透露苹果正在为其智能眼镜项目评估至少四款不同的镜框设计。面对雷朋与Meta合作的智能眼镜已经抢占的先机,苹果显然打算拿出自己的看家本领——顶级的工业设计和强大的生态整合能力,来一场漂亮的“后发制人”。
金山办公 2026 年(一季报)业绩预告 营收 15.65亿元到16.62亿元、同比增长20.24%到27.68%,净利润 20.22亿元到23.07亿元
金山办公2026年Q1业绩预告解读:营收稳健增长,净利润同比激增超4倍 4月14日,金山办公正式发布了2026年第一季度业绩预告。公告显示,公司在本季度展现出强劲的经营韧性,核心财务指标预计均实现大幅跃升,尤其是盈利能力呈现爆发式增长。 具体财务预测如下:公司预计第一季度营业总收入将达到15 65亿
长城魏牌 V9X 标轴版车型官图公布,4 月 16 日开启预售
长城魏牌 V9X 标轴版官图发布,4月16日开启预售 4月10日,长城汽车旗下魏牌正式揭晓了V9X标轴版车型的官方图片。这款备受关注的新车轴距设定为3050mm,并已确定将于4月16日启动预售。 先看外观,标轴版车型完整延续了品牌标志性的“东方经典建筑美学”设计语言。车头部分,发光悬浮车标的设计颇为
保时捷 2026 年一季度全球交付量同比下滑 15%,中国市场暴跌 21%
保时捷2026年开局遇冷:转型阵痛与市场寒流 2026年的春天,对于跑车巨头保时捷而言,似乎有些寒意。最新数据显示,这家以性能著称的制造商在第一季度全球仅交付了60,991台新车,与去年同期相比,下滑幅度达到了15%。 这盆冷水,主要浇在了两个关键市场:中国和北美。尤其是其电动化板块,未能扛起增长大
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

