java拓展类方式的选择

1、代理是继承和组合的中庸之道
有时候继承会有一些安全问题,特别是跨包的继承,这个后面我们详细讲解。这时候就只能使用组合的方式拓展类,但是组合的缺点是没有暴露出父类的方法,虽然你定义了父类的域,但是新的拓展类中看不到父类的方法,这时候代理,或者说委托方式就出现了。话不多说,上代码!
下面展示一些 内联代码片

/**
 * 飞船控制类
 */
public class SpaceShipControl {
    void up(int step) {
        System.out.println("move up" + step);
    }

    void down(int step) {
        System.out.println("move down" + step);
    }

    void left(int step) {
        System.out.println("move left" + step);
    }

    void right(int step) {
        System.out.println("move right" + step);
    }

}

/**
 * 飞船委托类
 */
public class SpaceShipDelegation {
    /**
     * 被代理对象
     */
    private SpaceShipControl spaceShipControl = new SpaceShipControl();

    private String name;

    public SpaceShipDelegation(String name) {
        this.name = name;
    }

    /**
     * 委托转发
     * @param step
     */
    public void up(int step){
        spaceShipControl.up(step);
    }

    public void down(int step){
        spaceShipControl.down(step);
    }

    public void left(int step){
        spaceShipControl.left(step);
    }

    public void right(int step){
        spaceShipControl.right(step);
    }
}

上面的例子中,通过委托转发的方式暴露了飞船控制类的方法,这样既清晰又避免了直接的继承!
2、继承还是复合的选择
上面的委托类实际上是基于复合的方式,或者说组合,那么两者之间怎么选择呢?优先推荐组合,上面说了继承会有安全问题,那么会有什么问题呢?如果让你拓展一个hashSet,并记录试图插入元素的数量addCount。你可能会这样设计(这里借用effectivJava里的例子):
下面展示一些 内联代码片

public class InstrumentedSetNotSafe<E> extends HashSet<E> {
    private int addCount = 0;

    public InstrumentedSetNotSafe() {
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> collection) {
        addCount += 3;
        return super.addAll(collection);
    }

    public int getAddCount() {
        return addCount;
    }

    public static void main(String[] args) {
        InstrumentedSetNotSafe<String> notSafe = new InstrumentedSetNotSafe<>();
        notSafe.addAll(Arrays.asList("a","b","c"));
        System.out.println(notSafe.getAddCount());
    }
}

看上去好像ok,但是上述代码的运行结果可能让你大吃一惊!因为allAll方法中调用的是super.addAll,而addAll方法里其实调用的是add方法,但是根据动态绑定机制(多态的体现)add方法调用的实际上是notSafeSet的add方法,不是吗?而不是hashSet的add方法!这时候就可以考虑组合了,采用第一条建议使用代理。
下面展示一些 内联代码片

//委托类
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> set;

    ForwardingSet(Set<E> set) {
        this.set = set;
    }


    @Override
    public int size() {
        return set.size();
    }

    @Override
    public boolean isEmpty() {
        return set.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return set.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
        return set.iterator();
    }

    @Override
    public Object[] toArray() {
        return set.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return set.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return set.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return set.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return set.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return set.addAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return set.retainAll(c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return set.retainAll(c);
    }

    @Override
    public void clear() {
        set.clear();
    }

    @Override
    public int hashCode() {
        return set.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        return set.equals(o);
    }
}
//包装
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount;

    public InstrumentedSet(Set<E> set) {
        super(set);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> collection) {
        addCount += 3;
        return super.addAll(collection);
    }

    public int getAddCount() {
        return addCount;
    }
}

上面其实还用到了包装模式!!其实组合还有一个优点,就是灵活,他可以动态选择类型,如上面的构造器里传递的是set接口,可能是hashSet,可能是treeSet。
3、自己思考是is-a的关系还是has-a的关系
只有真正是子类型的时候才推荐使用继承的方式,一般用于继承体系的框架中。如果你感觉到模棱两可,最好使用组合的方式。如果你在应该使用组合的情况下使用了继承,则会暴露不必要的细节,导致语义混乱。比如java类库中的Properties就是使用了继承HashTable,Properties目的是存取String类型key和value,客户端应该使用的是getProperty()方法,该方法会返回一个String字符串给客户端,但是客户端也可以使用父类HashTable的get方法,他被暴露在子类中,而get方法则不一定返回String的value值了。虽然继承很强大,但它可能破坏封装的原则。
4、如果采用继承,请一定提供良好的文档说明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值