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.__agep = Person("andy")# 可以访问 print(p.name) #私有属性, 无法访问 print(p.__age) #私有方法, 无法访问 print(p.__secret())
可是国王却说:"嗯, 这种方式挺简单的嘛, 用下划线就实现了, 很简洁, 我们能不能也这样啊?"
大臣乙有点瞧不起这个脚本语言, 他赶紧说:"万万不可, 陛下有所不知, 这个 Python 啊, 即使加了下划线, 也只是'伪私有'的属性和方法."
- "什么是伪私有?"
- "就是说外界依然有方法访问他们!"
- # 用这种方法, 依然可以访问伪私有属性和方法 print(p._Person__age) # 10print(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 使者半天都没有开口了, 他心里一直在琢磨, 我是不是有点落 5 了? Python 有模块, Ruby 也有模块, 这 Java 的模块化更是搞得如火如荼. 模块化极大地提升了封装性, 如果想进行大型项目的开发这模块化是少不了的, 想想自家那凌乱不堪的 js 文件, 是时候做出改变了......
来源: https://juejin.im/entry/5ba1a493f265da0aa41e63d9