单分派和多分派

在学习访问者模式时,看到了single dispatch(单分派),double dispatch(双分派)和multi dispatch(多分派)的概念,这里转载一篇网上讲的关于单分派和多分派的文章:



一:分派的概念 
变量被声明时的类型叫做变量的静态类型,或叫做明显类型,而变量所引用的对象的真实类型叫做实际类型,这种根据对象的类型而对方法进行的选择,就是分派.分派是面向对象的语言所提供的关键特性之一,根据分派发生的时期 ,分为静态分派和动态分派. 
静态分派发生在编译时期,分派根据静态类型信息发生,如方法重载就是静态分派 
动态分派发生在运行时期,动态分派动态地转换掉某个方法,面向对象的语言利用动态分派来实现方法转换产生多态性. 
(a)方法重载----墨子骑马 

Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2. public abstract class Horse {  
  3.   
  4. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class BlackHorse extends Horse{  
  4.   
  5. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class WhiteHorse extends Horse{  
  4.   
  5. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class Mozi {  
  4. private Horse lnkHorse;  
  5. public void ride(Horse h) {  
  6. System.out.println("Riding a horse");  
  7. }  
  8. public void ride(WhiteHorse wh) {  
  9. System.out.println("Riding a white horse");  
  10. }  
  11. public void ride(BlackHorse bh) {  
  12. System.out.println("Riding a black horse");  
  13. }  
  14. public static void main(String[] args) {  
  15. Horse wh = new WhiteHorse();  
  16. Horse bh = new BlackHorse();  
  17. Mozi mozi = new Mozi();  
  18. mozi.ride(wh);  
  19. mozi.ride(bh);  
  20. }  
  21. }  

显然Mozi类的ride方法是由三个方法重载而成的,这三个方法分别接收马,白马,黑马。两次调用ride方法输出的都是Riding a horse.想下,两次对ride方法的调用传入的是不同的参量,也就是wh和bh,它们虽然具有不同的真实类型,但是它们的明显类型都是House类型。重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成 了.这就是静态分派的例子. 


(b)动态分派--Java通过方法的转换(Overriding)支持动态分派 
请看下面的代码: 
Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class DongTaiFenPai {  
  4. public static void main(String[] args){  
  5. String s1 = "ab";  
  6. Object o = s1 "c";  
  7. String s = "abc";  
  8. boolean b = o.equals(s);//o的真实类型为String  
  9. System.out.println(b);  
  10. }  
  11. }  


上面的例子中,变量s1和s的静态类型和真实类型都是String,而o的静态类型是Object,真实类型则是Object类型的 一个子类型。分析:  如果上面最后一行的equals方法调用的是String类的equals方法,那么上面代码检查的就是o的值是否博字符串"abc"  ,相反,如果上面的equals方法调用的是Object类的equals方法,那么检查的就是o所指的对象和s1所指的对象是不 是同一个对象.  所以问题的核心是Java编译器在编译时期并不总是知道哪一些代码会被执行,因为编译器仅仅知道对象的静态类型,  而不知道对象的真实类型,而方法的调用则是根据真实类型(o的真实类型为String),而不是静态类型。变量o指向一  个类型为String的对象,这个String对象的值是"abc",这样一来,一面最后一行的equals方法调用的是String类的  equals方法,而不是Object类的equals方法. 

二:分派的类型 
一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参量统称为方法的宗量.根据分派可以基于多少种宗 量,可以将面向对象的语言分为单分派语言和多分派语言.单分派语言根据一个宗量的类型进行对方法的选择,多分 派语言根据多于一个宗量的类型对方法进行选择.Java是单分派语言,因为它动态分派仅仅会考虑到方法的接收者类 型,同时又是静态的多分派语言,因为它对重载方法的分派会考虑到方法的接收者类型以及方法的所有参量的类型.  在一个支持动态单分派的语言里(如Java),有两个条件决定了一个请求会调用哪一个操作,一是请求的名字,二是接 收者的真实类型,单分派限制了方法的选择过程,使得只有一个宗量可以被考虑到,这个宗量通常就是方法的接收者 。总之,Java语言支持静态的多分派和动态的单会派. 

三:一个静态分派和动态分派的例子 
下面的例子说明了在Java语言中静态多分派和动态单分派是如何发生的: 
Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. import java.awt.Canvas;  
  4.   
  5. public class Point {  
  6. private int x;  
  7. private int y;  
  8. public Point() {  
  9. }  
  10. public void draw(Canvas c) {  
  11. // write you code here  
  12. }  
  13. public void translate(int d) {  
  14. x = d;  
  15. y = d;  
  16. }  
  17. public void translate(int dx, int dy) {  
  18. x = dx;  
  19. y = dy;  
  20. }  
  21. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. import java.awt.Canvas;  
  4. import java.awt.Color;  
  5.   
  6. public class ColorPoint extends Point {  
  7. private Color c;  
  8. public ColorPoint() {  
  9. }  
  10. public void draw(Canvas C) {  
  11. // Color point code  
  12. }  
  13. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. import java.awt.Canvas;  
  4.   
  5. public class Client {  
  6. private static Point p;  
  7. private static Point pc;  
  8. public static void main(String[] args) {  
  9. p = new Point();  
  10. pc = new ColorPoint();  
  11. // static multi-dispatch  
  12. p.translate(5); // one int version  
  13. p.translate(12); // two int version  
  14. // dynamic uni-dispatch  
  15. Canvas aCanvas = new Canvas();  
  16. p.draw(aCanvas); // Point.draw()  
  17. pc.draw(aCanvas); // ColorPoint.draw();  
  18. }  
  19. }  


从Client可以看出,由于方法的重载,对Point对象的两种translate方法的调用是典型的静态多分派,而对子类以及 父类的draw方法调用则是典型的动态单分派. 

四:双重分派 
一个方法根据两个宗量的类型来决定执行不同的代码,这就是"双分派"或者"双重分派",Java语言不支持动态的多分 派,但是通过设计模式也可以在Java语言里实现动态的双重分派.双重分派是多重分派的一种,双重分派是由连续的 两次单分派组成的。怎么样在Java语言中实现动态的双重分派呢?下面看下用"返传球"实现双重分派: 
(a)返传球 

仔细想想,既然Java语言支持动态的单分派,那么为什么不可以通过两次方法调用来达到两次分派的目的呢?假设有两个类West和East,这两个类分别有两个方法,叫做goWest和goEast,现在west对象首先调用east对象的goEast方法并将自己传入,在east对象被调用时,立即根据传入的参量知道调用者是谁,于是反过来调用调用者对象的goWest方法,通过两次调用将程序控制权轮番交给两个对象,这样就出现了两次方法调用,程序的控制权被两个对象像传球一样,首先由west对象传给了east对象,然后又被返传给了west对象。 


(b)方法的置换 
仅仅返传了一下球,并不能解决双重分派的问题,关键是怎么利用这两次调用,以及Java语言的动态单分派功能,使 得在这种传球过程中能够触发两次(动态)单分派.  动态单分派在Java语言中是在子类型将父类型的方法置换掉时发生的,换言之,West和East都必须分别置身于自己的 类型等级结构中.于是在这个系统里,有一个客户端,以及两个等级结构---West等级结构和East等级结构,在West等 级结构中,由West定义出公共的类型,并有两个具体子类实现了抽象类的接口,而East等级结构也与此相像. 
Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public abstract class East {  
  4. public abstract void goEast(West west);  
  5. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class SubEast1 extends East {  
  4. public void goEast(West west) {  
  5. west.goWest1(this);  
  6. }  
  7.   
  8. public String myName1() {  
  9. return "SubEast1";  
  10. }  
  11. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class SubEast2 extends East {  
  4. public void goEast(West west) {  
  5. west.goWest2(this);  
  6. }  
  7.   
  8. public String myName2() {  
  9. return "SubEast2";  
  10. }  
  11. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public abstract class West {  
  4. public abstract void goWest1(SubEast1 east);  
  5. public abstract void goWest2(SubEast2 east);  
  6. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class SubWest1 extends West {  
  4. public void goWest1(SubEast1 east) {  
  5. System.out.println("SubWest1 " east.myName1());  
  6. }  
  7. public void goWest2(SubEast2 east) {  
  8. System.out.println("SubWest1 " east.myName2());  
  9. }  
  10. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. public class SubWest2 extends West {  
  4. public void goWest1(SubEast1 east) {  
  5. System.out.println("SubWest2 " east.myName1());  
  6. }  
  7. public void goWest2(SubEast2 east) {  
  8. System.out.println("SubWest2 " east.myName2());  
  9. }  
  10. }  


Java代码   收藏代码
  1. package cai.milenfan.basic.test;  
  2.   
  3. import java.awt.Canvas;  
  4.   
  5. public class Client {  
  6. private static East east;  
  7. private static West west;  
  8. public static void main(String[] args) {  
  9. // combination 1  
  10. east = new SubEast1();  
  11. west = new SubWest1();  
  12.   
  13. east.goEast(west);  
  14.   
  15. // combination 2  
  16. east = new SubEast1();  
  17. west = new SubWest2();  
  18.   
  19. east.goEast(west);  
  20.   
  21. }  
  22. }  
  23.   
  24. output:  
  25. SubWest1 SubEast1  
  26. SubWest2 SubEast1  

系统运行时,会首先创建SubWest1和SubEast1对象,然后客户端调用SubEast1的goEast()方法,并将SubWest1对象传 入(由于SubEast1对象置换了其超类East的goEast方法,因此这个时候就发生了一次动态单分派)。当SubEast1对象接 到调用时,会从参数中得到SubWest1对象,所以它就立即调用这个对象的goWest1方法,并将自己传入(由于SubEast1 对象有权选择调用哪一个对象,因此在此时又进行一次动态的方法分派)  由output知道,两个名字一个来自East等级结构,一个来自West等级结构,困此它们的组合式是动态决定的,这就是 动态双重分派的实现机制.  最后需要指出的是,首先East和West等级结构的大小是彼此独立的,虽然在上面的例子中两者都包含了两个具体成员  ,但这只是巧合。其次West要访问的East等级结构中的节点数目应当与West对象所配备的方法的数目相等,这是必然 的.  这种返传球的设计就是访问者模式的精华

<think>好的,我现在需要回答用户关于Python中单分派与策略模式的使用及区别的问题。首先,我需要明确这两个概念的定义它们在Python中的具体实现方式。单分派(Single Dispatch)通常指的是根据函数的第一个参数的类型来决定调用哪个函数实现,这可以通过Python的functools模块中的singledispatch装饰器来实现。而策略模式是一种设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,让算法的变化独立于使用它的客户端。 接下来,我需要对比两者的使用场景。单分派适用于需要根据不同类型参数动态选择不同函数实现的场景,尤其是在处理不同类型的数据时,比如处理不同类型的数据序列化。而策略模式适用于需要在运行时选择不同算法或策略的情况,比如不同的促销策略或排序算法。 然后,我需要考虑它们的实现方式。单分派在Python中通过装饰器实现,代码结构较为简洁,主要依赖函数重载。而策略模式通常需要定义抽象策略类或接口,并实现具体策略类,然后通过上下文类来管理切换策略,这可能涉及更多的类对象之间的交互。 用户可能还希望了解它们之间的区别,比如单分派是基于参数类型的动态分派,而策略模式是基于对象组合委托的模式。此外,单分派更侧重于函数的重载,而策略模式侧重于算法的封装替换。 需要确保回答中引用提供的参考资料,比如引用[1]中提到的策略模式实现,以及引用[4]中提到的策略模式在Spring中的应用,但这里需要结合Python的具体情况。同时,用户可能对如何在实际项目中应用这两种模式感兴趣,需要给出代码示例来说明两者的不同实现方式。 还需要检查是否有相关的注意事项,比如性能考虑、代码可维护性等。例如,策略模式可能更适合需要频繁添加新策略的情况,而单分派在类型处理上更为灵活,但可能不适合复杂的策略管理。 最后,要确保回答结构清晰,分点说明,并且符合用户要求的格式,包括使用中文、正确的LaTeX格式,以及生成相关问题。需要验证代码示例的正确性,并确保引用标识正确添加。</think>### Python 单分派与策略模式解析 #### 一、核心概念与实现 1. **单分派 (Single Dispatch)** - **定义**:根据函数第一个参数的类型动态选择实现方法,属于**泛函数**的一种实现方式。 - **Python实现**:通过`functools.singledispatch`装饰器实现多态行为。 ```python from functools import singledispatch @singledispatch def process_data(data): raise NotImplementedError("Unsupported type") @process_data.register(int) def _(data): return f"Processing integer: {data}" @process_data.register(str) def _(data): return f"Processing string: {data}" ``` 2. **策略模式 (Strategy Pattern)** - **定义**:将算法族封装为独立对象,支持运行时动态切换策略[^1]。 - **Python实现**:通过抽象基类定义策略接口,具体策略类实现算法。 ```python from abc import ABC, abstractmethod class CompressionStrategy(ABC): @abstractmethod def compress(self, data): pass class ZipStrategy(CompressionStrategy): def compress(self, data): return "ZIP compressed" class RarStrategy(CompressionStrategy): def compress(self, data): return "RAR compressed" class Compressor: def __init__(self, strategy: CompressionStrategy): self.strategy = strategy def execute(self, data): return self.strategy.compress(data) ``` --- #### 二、关键差异对比 | 维度 | 单分派 | 策略模式 | |-----------------|-------------------------------|-------------------------------| | **设计目标** | 基于参数类型扩展函数行为 | 封装可替换的算法族 | | **实现方式** | 函数重载(类型分派) | 对象组合(接口与实现分离) | | **适用场景** | 处理不同类型参数的统一操作 | 需要动态切换算法策略的场景 | | **代码耦合度** | 低(集中式类型处理) | 低(策略与上下文解耦) | | **扩展性** | 新增类型需注册新函数 | 新增策略需实现接口 | --- #### 三、典型应用场景 1. **单分派适用场景** - 数据序列化:根据数据类型(如`int`、`str`、自定义类)选择不同序列化逻辑。 - 数学运算重载:例如对不同数值类型(标量、向量、矩阵)实现统一的运算符。 2. **策略模式适用场景** - 促销策略选择:根据订单属性(金额、商品类型)动态选择折扣算法[^1]。 - 压缩算法切换:在ZIP、RAR等压缩格式间动态切换。 - 爬虫解析器选择:针对不同网站结构使用不同解析策略[^4]。 --- #### 四、技术选型建议 1. 选择**单分派**若: - 行为差异主要由**参数类型**驱动 - 需要保持函数调用的统一接口 - 示例:实现`json.dumps()`对不同数据类型的处理 2. 选择**策略模式**若: - 需要支持运行时策略切换 - 算法复杂度高需独立封装 - 示例:电商系统中会员等级对应不同折扣规则[^1] --- #### 五、混合使用案例 ```python from functools import singledispatch from typing import List # 单分派实现基础解析 @singledispatch def parse_content(content): raise ValueError("Unsupported content type") @parse_content.register(str) def _(text): return f"Text parsed: {text[:50]}..." @parse_content.register(bytes) def _(binary): return f"Binary decoded: {binary.hex()[:10]}..." # 策略模式封装解析器 class ParserStrategy: def __init__(self, content): self.content = content def execute(self): return parse_content(self.content) # 上下文调用 strategy = ParserStrategy(b"x00x01x02") print(strategy.execute()) # 输出: Binary decoded: 000102... ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值