`
yangdong
  • 浏览: 65065 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java 下面如何模拟友元

    博客分类:
  • Java
阅读更多
Java 没有像 C++ 一样的友元。但是友元我认为是非常有用的。尤其是在设计 API 的时候,参见《Practical API Design》。友元可以帮助实现“clueless programming”(即无需考虑过多繁琐的细节就可以编程)。但是也容易被滥用。无论如何,我希望这个选择是在语言使用者手里。

《Practical API Design》里提到了一种 Accessor 模式,可以帮助 API 定义实现友元。但是这个并不是我们通常意义上的友元。因为它只能让客户端不能访问 API 的非 public 方法,但是 API 内部实现的任意部分都可以。更糟糕的是,它的实现复杂,而且需要自定义的运行时容器的配合。下面的方法非常短,而且可以指定一个友元类。灵感来源于 Accessor 模式。
package ydong.javatest;

import ydong.javatest.impl.TrueFriend;

public class Main {

    public static void main(String[] args) throws Exception {
        new TrueFriend().call();
    }

    private void onlyFriendCanCall(String msg) {
        System.out.println("friend called: " + msg);
    }

    public abstract static class Friend {
        public Friend() {
            if (this.getClass() != friend) {
                throw new UnsupportedOperationException();
            }
        }

        private static final Class<? extends Friend> friend = TrueFriend.class;

        protected final void onlyFriendCanCall(Main main, String msg) {
            main.onlyFriendCanCall(msg);
        }
    }

}

Main 的 onlyFriendCanCall 方法要求只能被 TrueFriend 类调用。原理就是利用内部类可以访问外部类的私有成员来将友元问题转换成父类识别子类类型的问题。
package ydong.javatest.impl;

import ydong.javatest.Main;

public final class TrueFriend extends Main.Friend {

    public void call() {
        this.onlyFriendCanCall(new Main(), "TrueFriend");
    }

}

TrueFriend 不需要跟 Main 在同一个包下面。它只要继承 Main.Friend 就可以访问到 Main.Friend.onlyFriendCanCall 方法。继而间接地访问到 Main.onlyFriendCanCall。为什么要用继承?因为继承可以让 Main.Friend 拿到实际调用者--TrueFriend 的类型,进而去判断是否是友元类。普通的方法调用如果不用类似 AOP 的机制是没有办法拿到这个信息的。你可以写一个 FalseFriend 来试试看效果?

Friend 的代码非常短,可以直接背下来了。用的时候只要记得原理可以很快写出来。你可以将其扩展成指定哪些方法可以被哪些友元类(可以是多个)调用。

这个方法有个明显的缺点:如果目标友元类已经继承了某个类就不行了。虽说是从南墙撞到了北墙,不过这个方法已经可以解决很多问题了。很多情况下你可以想办法调整设计使得目标友元类没有其它父类。至少在我的项目里可以做到。如果有人找到了更好的方法请一定要告诉我。

虽然文章放在设计模式的类目下面,但是我并不确定这个是否真的是模式。就算是也不知道该叫什么。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics