Java 帝国第一代国王正式登基,百官前来朝贺。
大臣甲说道:“恭喜陛下登基,为了吸引更多程序员加入我国,臣建议尽快完善我们Java语言的OOP特性,封装、继承、多态。”
国王说:“一个个来,先说说封装吧, 我们现在已经可以把数据和方法放到一个类中,接下来想办法隐藏信息,限制对他们的访问了, 我听说现在有不少人在使用C++, 能不能从它借鉴一下啊? ”
大臣乙对C++很有好感,他说:“陛下圣明,C++那里有pubic, private,protected 等关键字,可以用于修饰属性和方法,我们直接拿过来用得了。”
public class Person{
private String name;
private int age;
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
“如此甚好!” 国王表示赞许,不过他眼珠一转,突然想到了早些年出现的Python, 他问道:“Python是怎么处理封装这个问题的?”
大臣甲从Python王国“倒戈”而来,怀着对故国的歉意,十分想把Python的语法带来一点给Java,听到国王这么问起,赶紧说到:“Python的处理比较简单,用两个下划线来表示私有的属性和方法。”
class Person:
def __init__(self, name):
self.name = name
# 私有属性
self.__age = 10
# 私有方法
def __secret(self):
return self.__age
p = Person("andy")
#可以访问
print(p.name)
#私有属性,无法访问
print(p.__age)
#私有方法,无法访问
print(p.__secret())
可是国王却说:“嗯,这种方式挺简单的嘛,用下划线就实现了,很简洁,我们能不能也这样啊?”
大臣乙有点瞧不起这个脚本语言,他赶紧说:“万万不可,陛下有所不知,这个Python啊,即使加了下划线,也只是‘伪私有’的属性和方法。”
“什么是伪私有?”
“就是说外界依然有方法访问他们!”
#用这种方法,依然可以访问伪私有属性和方法
print(p._Person__age) # 10
print(p._Person__secret()) # 10
“这算哪门子私有的属性和方法? 一点都不纯粹。” 大臣乙继续补刀。
国王说:“好吧,那不学它了, 那JavaScript呢?他是怎么实现封装的?”
朝中的大臣们面面相觑,JavaScript? 这是什么鬼?怎么没有听说过?
(码农翻身注:JavaScript的出现时间比Java要晚, 这个Java国王估计是穿越了。)
把类隐藏起来
大臣甲看到自己的想法没有“得逞”,又另辟蹊径:“陛下,Python有module的机制,可以把多个类组织到一起,形成一个高内聚的单元,我们Java要不也这么干?”
国王瞪了大臣甲一眼,训斥道:“不要什么都学Python! 我们也得有点独特的东西啊。对于如何组织class, 我们可以用package,一个package对应文件系统的一个目录,下面可以有多个class文件。 如果一个类没有被public 修饰,那他只能被同一个package下面的类访问,其他package的类是访问不到的。这个设计不错吧?!”
国王甚为得意。
同一个package下有三个类A,B,C, 只有class A能被外边的包访问到,可以充当这个包对外的“接口” (注:不是java 的interface ), B,C只是包级可见的类, 相当于包内部的实现了,外界是无法new 出来, 防止了被外界误用。
只要保证A不发生变化,就不会影响外界使用, B和C想怎么改就怎么改!
类的朋友
大臣甲小心地问道:“如果我只想把foo.core中的class B暴露给foo.cmd访问,同时阻止别的包访问class B,该怎么办呢? ”
“怎么会有这么‘变态’的需求? ” 朝中各位大臣都表示不可思议。
国王沉吟道:“程序员的要求是无穷无尽的,例外总是会发生的, 这种需求是存在的, 容朕想想。”
熟悉C++的大臣乙赶紧上奏:“陛下,C++ 有个什么friend class的概念。 例如在class Node 中声明了 friend class LinkedList , 那LinkedList 就可以访问Node 类的属性和方法了。 ”
大臣甲强烈反对这种做法:“不好不好,虽然看起来给程序员提供了方便,但是给封装性撕开了一个大口子,如果被滥用,后果不堪设想。”
国王表示同意:“对,还是放弃这种想法吧,保持简单性最重要。 如果他实在想访问class B,可以采用两种办法:(1) 把 class B 变成 public (2) 通过接口class A来进行代理。”
模块化
斗转星移,转眼间Java国王已经传到了第9世。
这一天,邻国的Python, JavaScript派使者来访,受到了国王的热情招待,席间谈到了java package的存在的问题。
Java package的方式虽然不错,可是还是有很大的弊端,其中最大的弊端就是很多包中的类都是public的, 这就造成了这样一种情况。
本来是想让org.foo.api对外提供接口,让Client去调用的,但实际上,只要foo.jar放到classpath中,另外两个package , org.foo.impl, org.foo.core中的类也就暴漏了。
JavaScript使者说道:“奥, 我原来以为贵国的一个jar文件就是一个可复用的模块, 现在看来还是远远不够啊!”
“怪不得大家都说,你的一个jar文件就是class的压缩包, classpath就是把这些类给平铺而已。” Python使者笑道。
Java国王心中有点生气,但是脸上没有表露出来:“贵国是怎么实现的啊?”
Python使者想了想, 自家的module好像也差不多,并且只能靠约定(给变量和方法的前面添加下划线)的方式来实现private , 由于是约定的,外界依然可以访问。
JavaScript想到自己连module, package都没有,赶紧噤声。
Java 国王说:“简单的jar文件缺乏一个重要的特性:隐藏内部实现, 寡人打算做一个重要的改变,定义真正的模块!”
“看到没有? 我打算用一个文件module-info.java来定义一个模块中有哪些包是可以被export的, 只有那些export的包才能被Client所调用, 其他的包对Client都是不可见的。 ”
看到这个设计方案,各个大臣都觉得不错。 有了模块, 就真正地定义了对外可以访问的接口,除了接口的那个package之外,其他的package是不可访问的, 彻底实现了封装。
ServiceLoader
Python使者盯着这个图看了一会儿,说道:“不对吧,假设有这样的代码:”
FooService service = new FooServiceImpl();
“其中FooService 是org.foo.api包下面的类, FooServiceImpl是org.foo.impl下面的类, 按照你模块化的要求,这个FooServiceImpl是不能被Client所访问的, 那怎么才能创建FooService呢?”
Java国王心想这Python使者对我Java语言挺熟悉的啊,搞得我下不来台。
“陛下,臣以为可以用工厂模式解决!” 终于有大臣前来救驾。“创建一个新的类FooServiceFactory,把它放到org.foo.api包下,可以公开调用,这样不就行了?”
public class FooServiceFactory{
public static FooService getFooService(){
return new FooServiceImpl();
}
}
Python使者却继续施压: “不过让人不爽的是, 这个FooServiceFactory虽然属于api包,但是需要知道impl包的具体实现。 如果想添加一个FooService的实现,还得修改它。还是不妥啊!”
突然,Java国王拍了一下脑袋,对了,我怎么把ServiceLoader给忘记了呢?
我可以把原来的模块分成两个模块,org.foo.api表示接口, org.foo.provider表示实现。
在org.foo.provider中特别声明,本模块可以提供FooService的实现!
provides org.foo.api.FooService with org.foo.provider.FooServiceImpl
Python使者还是不太明白: “那客户端怎么使用呢?”
“简单,Client 代码可以这么写:”
Iterable<FooService> iter =
ServiceLoader.load(FooService.class);
遍历iter,获取FooService并且使用。
这样在运行时,Client的代码就可以使用ServiceLoader就可以找到这些具体的实现了, 当然实现可能不仅仅只有一个,Client选择一个就可以了。
当然,JDK必须得实现这个ServiceLoader,去获取这些具体的实现。
“这个方案,即不破环封装性,又提供了足够的灵活性,相当于在运行时装配对象,陛下圣明!” 大臣们纷纷拍马屁。
Python使者见状,也就不再发言,开始低头喝酒。
JavaScript 使者半天都没有开口了,他心里一直在琢磨,我是不是有点落伍了? Python有模块,Ruby也有模块,这Java的模块化更是搞得如火如荼。模块化极大地提升了封装性,如果想进行大型项目的开发这模块化是少不了的, 想想自家那凌乱不堪的js文件, 是时候做出改变了......
(完)