Java 国王:我来告诉你什么才是真正的封装!

1,867 阅读8分钟
原文链接: mp.weixin.qq.com

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 使者半天都没有开口了,他心里一直在琢磨,我是不是有点落伍了? Python有模块,Ruby也有模块,这Java的模块化更是搞得如火如荼。模块化极大地提升了封装性,如果想进行大型项目的开发这模块化是少不了的, 想想自家那凌乱不堪的js文件, 是时候做出改变了......

(完)

码农翻身,用故事讲解技术本质, 更多精彩文章,请移步《码农翻身三年文章精华