第 6 章 访问权限控制
权限控制的产生背景
类库设计者难免想重构代码,但是类库使用者却希望代码在某些方面保持不变。权限控制就是把代码中变动的代码与保持不变的代码区分开来。
代码编译
当编译一个 .java 文件时,在 .java 文件中的每个类的哦会有一个输出文件,而输出该文件的名称与 .java 文件中每个类的名称相同,只是后面多了一个 .class。所以编译单个的文件可能会得到大量的.class 文件。如果用编译型语言编写过程序,对于编译器产生一个中间文件,然后再与通过链接器(用以创建一个可执行文件)产生的其他同类文件捆绑在一起。但是 Java 不是这样的,Java可运行程序是一组可以打包并压缩为一个 Java 文档文件(JAR,使用 Java 的 jar文档生成器)的 .class 文件。Java 解释器负责这些文档的查找、装载和解释。
java 解释器的运行过程如下:首先,找到环境变量 CLASSPATH(可以通过操作系统设置)CLASSPATH 包含一个或多个目录,用作查找 .clas 文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换成反斜杠,以从 CLASSPATH 根中产生一个路径名称。得到的路径会与 CLASSPATH 中的各个不同的项相连,解释器就会在这些目录中查找与你所要创建的类名称相关的 .class 文件。
// 如我的域名是 zzjack.com -> 把它倒置 -> com.zzjack
// 创建一个名为 simple 的包,所以包名就是
package com.zzjack.simple
package 必须是第一行非注释代码。
Java 访问权限修饰词
如果不提供任何权限的修饰此,则默认是“包访问权限”
- 包访问权限
同一个包的可以自由访问 - public 访问权限,暴露 API
- private:无法访问,私有方法
- protected:继承访问权限
访问权限的控制常被称为具体实现的隐藏,把数据和方法包装进类中,以及具体实现的隐藏,常共同称为是封装。其结果是一个同时带有特征和行为的数据类型。
这样做启示我们在阅读代码时,首先阅读最为重要的时 public 成员,因为可以从文件外部调用它们,遇到作为内部实现细节的非 public 成员时可以停止阅读。
继承
继承不是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者的区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。
不含参数的初始化
class Art{
Art(){
System.out.println("Art constructor");
}
}
public class Soup1 extends Art{
Soup1(){
System.out.println("Drawing constructor");
}
}
class Cartoon extends Soup1{
// Cartoon(){
// System.out.println("Cartoon constructor");
// }
}
Cartoon ca = new Cartoon();
基类会进行初始化,即便子类没有构造器,编译器也会自动生成一个,来调用基类的构造器。总之,编译器可以默认调用父类的无参数构造器,在子类中把父类进行初始化。
含参数的构造器
class Soup1{
Soup1(int i){
System.out.println("Game constructor " + i);
}
}
class BoardGame extends Soup1{
BoardGame(int i){
super(1);
System.out.println("BoardGame constructor " + i);
}
}
class Chess extends BoardGame{
Chess(){
super(11);
System.out.println("Chess constructor");
}
}
但是含参数的构造器,继承的子类必须用 super() 对父类进行初始化。
代理
编程中有一个思想:不要随便去修改别人写好的方法,如果要修改,可以使用代理的方式来扩展该方法。有时候可以使用代理来代替使用继承。
class Soup1{
void up(int velocity){}
void down(int velocity){}
void left(int velocity){}
void right(int velocity){}
}
class SpaceShipDelegation{
private String name;
private Soup1 controls = new Soup1();
public SpaceShipDelegation(String name){
this.name = name;
}
public void up(int velocity){
controls.up(velocity);
}
public void down(int velocity){
controls.down(velocity);
}
public void left(int velocity){
controls.left(velocity);
}
public void right(int velocity){
controls.right(velocity);
}
}
这是最简单的例子,在实际代码中静态代理不像 Java 编程思想中的那么简单,并且运用更广泛的是动态代理。
参考教程,知乎
使用接口创建代理
- 定义接口
public interface FontProvider{
String getFont(String name);
}
- 实现接口类
public class FontProvider implements FontProvider{
@Override
String getFont(String name){
return name;
}
}
- 抽象工厂作为代理
public abstract class ProviderFactory{
public static FontProvider getFontProvider(){
return new FontProviderFromDisk();
}
}
- 供抽象工厂支配的类
class FontProviderFromDisk implements FontProvider{
@Override
public String getFont(String name){
return name;
}
}
增加缓存功能
// 添加缓存功能
class CachedFontProvider implements FontProvider{
private FontProvider fontProvider;
private Map<String, String> cache = new HashMap<>();
public CacheFontProvider(FontProvider fontProvider){
this.fontProvider = fontProvider;
}
@Override
public String getFont(String name){
String font = cache.get(name);
if (font == null){
// 注意,此时
String font = fontProvider.getFont(name);
cache.put(name, font);
}
return font;
}
}
动态代理
使用动态代理
如果工厂函数要给每个类都添加缓存功能,那如果像以下这么实现,给每个类都加一个静态代理就非常繁琐了。
public abstract class ProviderFactory{
public static ImageProvider getImageProvider(){
return new CacheFontProvider(new ImageProvider());
}
public static MusicProvider getMusicProvider(){
return new CacheFontProvider(new MusicProvider());
}
public static FontProvider getFontProvider(){
return new CacheFontProvider(new FontProvider());
}
}
如果可以使用动态代理,就可以非常方便了。
class CachedProviderHandler implements InvocationHandler{
private Map<String, Object> cached = new HashMap();
private Object object;
public CacheProviderHandler(Object object){
this.object = obejct;
}
public Object invoke(Object proxy, Method method, Obejct[] args) throws Throwable{
Type[] types = method.getParameterTypes();
}
}
改造抽象类
public absract class ProviderFactory{
public static FontProvider getAbstractProvider(Object obj){
Class<FontProvider> targetClass = FontProvider.class;
return (FontProvider) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[] {targetClass}, new CachedProviderHandler(obj));
}
}
用这种方式就可以做到在抽象工厂中写一个就能把类加工。