objc与鸭子对象(下)
我是前言
这是《objc与鸭子对象》的下半部分,《objc与鸭子对象(上)》中介绍了鸭子类型和它在objc中的实践,以及一个使用NSProxy实现JSON Entity的鸭子类。下半部分介绍鸭子对象的进阶用法,并简单介绍由鸭子对象思想衍生出的依赖注入
,实现一个demo。
被误解了的面向对象
Smalltalk之父或者说面向对象之父(之一)的Alan Kay曾写过:
I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” - that is what the kernal of Smalltalk/Squeak is all about.
面向对象的思想的核心并不在于object
或者class
,而在于message
,或者说对象和类只是消息的载体。面向对象思想将程序按功能和逻辑分成了若干个类,每个类包含自己的代码、功能实现并提供对外接口,以黑箱模式运行,使得外部无需了解内部而协同使用,分解了复杂度并控制在一个个较小规模中,以消息作为其间所有的协作方式。
回到主题,理解了message才是全部,鸭子对象又可以更近一层,试想下整个程序,每个类除了知道自己的类之外其他类名一无所知,全部通过协议发消息:
1 | - (void)schoolWillStart:(id<School>)school |
Json Entity的重构
回想上一篇中的JSON Entity类:
1 | // XXDuckEntity.h |
干嘛caller要知道有这么个Class
存在呢?它关心的只是能用哪些message通信而已。于是把类声明移动到.m
中,简化成一个C的创建方法(类工厂方法同样会暴露类名):
1 | // XXDuckEntity.h |
如果这个类需要提供其他message接口供caller使用,则:
1 | @protocol XXDuckEntity <NSObject, NSCopying, NSCoding> |
<XXDuckEntity>
被注释掉是因为真实使用场景会造成类型不匹配造成编译警告,所以caller使用起来:
1 | NSString *json = @"{\"name\": \"sunnyxx\", \"sex\": \"boy\", \"age\": 24}"; |
这样重构的鸭子对象不仅隐藏了内部实现是个字典的事实,连它究竟是什么Class都隐藏了,但程序运行并无影响,骗一骗编译器罢了。不过这个思路的改变确引出另一个技术思路,那就是依赖注入
。
依赖注入
Dependency Injection
,简称DI
,其实在这个场景下叫动态实现注入
更合适。它的思想是将一个“对象”分成三部分,protocol、proxy和implementation,试想有两个协议,他们定义了彼此间该如何发送message:
运行时他们都是由proxy对象扮演:
但DI Proxy并不能响应任何message,真正的实现是动态被“注入”到Proxy中的:
由于调用层只有协议没有类名,所以Implement A
实现类并不依赖Implement B
,就像贩毒团伙的两方只靠小弟来交易,完全不知道幕后大哥是谁,这就是所谓的“面向接口编程”吧。
Let’s demo it
重点在实现这个Proxy类,按照刚才重构Json Entity类的思路,头文件定义十分精简:
1 | // XXDIProxy.h |
既然都叫Proxy了,再不使用NSProxy
类都对不起它了。这个类使用一个字典来存储被注入的实现对象,以及与protocol的对应关系:
1 | // XXDIProxy.m 这是个私有类 |
实现<XXDIProxy>
协议内容:
1 | // XXDIProxy.m |
关键步骤还是消息转发,非常简单,把收到的消息转发给能处理的implementation对象(如果用NSObject的forwardingTargetForSelector
将更加简单):
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel |
有了Proxy类,下面是另外两个角色的测试代码,协议:
1 | @protocol XXGirlFriend <NSObject> |
实现类(汉字是可以正常编译运行的- -):
1 | @interface 林志玲 : NSObject <XXGirlFriend> |
测试代码:
1 | 林志玲 *implementA = [林志玲 new]; |
这个简单的demo就完成了。
这个demo的源码
可以->从这里下载<-,have fun.
我是后语
现在有一个完整的依赖注入框架typhoon,感兴趣的可以把玩一下。
依赖注入不仅可以解耦依赖关系,也可以更好的Test和Mock,想测试某个对象只需要将实现对象注入成Test对象,想造假数据只需要将response对象替换成一个Mock对象,无需修改调用代码,天然不刺激~
PS: 实际使用中可不要过度设计哦。。。
Reference
http://c2.com/cgi/wiki?AlanKayOnMessaging
http://www.typhoonframework.org/