Java中的委托和继承(Delegation and Inheritance)

写在前面

上课的时候对delegation具体指什么一直有点蒙,看了一下课堂的ppt和CMU的课件,在这里对比Inheritance谈谈我理解的委托和继承。这里为了方便,delegation翻译为委托或委派,本文中的二者等价。

概念

委派和继承都是为了提高代码的复用性,只是方式不同。

  • 委派:一个对象请求另一个对象的功能,捕获一个操作并将其发送到另一个对象。
  • 继承:利用extends来扩展一个基类。

Delegation(委托)

委托依赖于动态绑定,因为它要求特定的方法可以在运行时调用不同的代码段,可以看下面代码:

public class A {
    void foo() {
        this.bar(); 
    }
    void bar() { 
        System.out.println("a.bar");
    }
}

在B类中,不使用继承,而是利用委托结合A,达到复用A类中代码的效果:

public class B {
    private A a;
    public B(A a) {
        this.a = a; 
    }
    void foo() {
        a.foo(); // call foo() on the a-instance }
    }
    void bar() {
        System.out.println("b.bar");
    }
}

考虑下面代码的输出:

A a = new A();
B b = new B(a); 
b.foo();
b.bar();

可以很容易地看出来结果:

a.bar
b.bar

这就是一种简单的委派机制。

此外,还有另一种委派的方式:
首先写一个接口,如下:

public interface List<E> { 
    public boolean add(E e); 
    public E remove(int index);
    public void clear();
    …
}

如果我们想在每次调用方法前,在控制台输出点提示信息,那么就可以这样做:

public class LoggingList<E> implements List<E> {
    private final List<E> list;
    public LoggingList<E>(List<E> list) { 
        this.list = list; 
    } 
    public boolean add(E e) { 
        System.out.println("Adding " + e); 
        return list.add(e); 
    } 
    public E remove(int index) { 
        System.out.println("Removing at " + index); 
        return list.remove(index); 
    }
    …
}

相当于重写了add方法,但是是实现接口而没有使用继承。

委派的几种类型归纳

  • Use (A use B)

  • Composition/aggregation (A owns B)

  • Association (A has B)

Dependency(依赖): 临时性的delegation

  • 在这种关系中,一个类使用另一个类而不将其作为一个属性。
  • 两类之间的这种关系称为“uses-a”关系。例如,它可以是一个参数,或者在一个方法中本地使用,参考下面的代码:

首先新建一个课程类:

public class Course {}

在课表类总使用课程类:

public class CourseSchedule {
    List<Course> courses = new ArrayList<>();
    public void add (Course c) {
        courses.add(c);
    }
    public void remove (Course c) {
        courses.remove(c);
    }
}

在这里,并没有将Course类作为CourseSchedule类的属性来使用,而是作为迭代器中的元素和方法中的参数来使用。

Association(关联): 永久性的delegation

  • 关联是类之间的持久关系,允许一个对象实例让另一个对象实例代表它自己做其他事。
  • 这种关系属于has-a的关系,是结构化的,因为它指定了一种对象与另一种对象相连接,并且不代表行为,即不在该类的方法中使用另一个类的方法,只是简单的将不同的对象连接起来。

下面展示一下关联的代码:

class Teacher {
    private Student [] students; 
}
class Student {
    private Teacher teacher;
    Course[] selectedCourses;
}

可以看到,在两个类中(Student和Teacher)互相都有彼此的实例,而且没有使用继承,就直接将这几个不同的类相连接,这就是利用了Association方式。

Composition: 更强的delegation

  • 组合是将简单对象或数据类型组合成更复杂的方法的一种方法。
  • 这种关系是a-part-of关系,一个类有另一个属性或实例变量——实现了一个对象包含另一个对象。

let`s show 代码:

class Heart {}
class Person { 
    private Heart heart = new Heart();
    public void operation () { 
        heart.operation(); 
    }
}
  • 这种方式理解起来就很简单了,直接在该类中实例化一个其他类,然后该调用方法调用方法,对这个实例想怎么用怎么用,十分灵活。
  • 不过需要注意的是:
  • 这里的实例是private的,也就是说,外界访问不到,这样的话,更改其值只能在该方法中;而且每次创建该类的对象时,就已经创建好这个类中的实例;也就是说一旦创建好该类的对象,其中的属性指向便已经创建好。

Aggregation

  • 聚集:对象存在于另一个之外,是在外部创建的,所以它作为一个参数传递给构造函数。
  • 这种关系是has-a的关系,区别于

    让我们看一个这个例子的代码:

class Student {}
class Course { 
    private Student[] students; 
    public addStudent (Student s) { 
        studtents.append(s); 
    } 
}

可以看到,在这里,内部的属性是可以在外部指定的,而不是完全依赖该类。

Composition vs. Aggregation

组合和聚集是最常用的两种delegation方式,可以说,其中的使用包括了依赖和关联方式,并在其上做了进一步的扩展。二者很相似,但又有很多不同之处,这里举一个例子看一下二者的最大不同之处:

public class WebServer { 
    private HttpListener listener; 
    private RequestProcessor processor; 
    public WebServer(HttpListener listener, RequestProcessor processor) {
        this.listener = listener;
        this.processor = processor;
    } 
}
public class WebServer { 
    private HttpListener listener; 
    private RequestProcessor processor; 
    public WebServer() { 
        this.listener = new HttpListener(80); 
        this.processor = new RequestProcessor(“/www/root”);
    } 
} 

可以看到,第一个代码是聚集的方式,第二个代码是组合的方式,这是为什么呢?

  • 在组合中,当拥有的对象被销毁时,所包含的对象也是如此。
    比如:
    一所大学拥有不同的部门,每个部门都有许多教授,因为是大学创建了这些部门,所以如果大学关闭,这些部门将不复存在,但这些部门的教授将继续存在。

  • 但是在聚集中,这并不一定是正确的:
    大学可以被看作是部门的组成,而院系则是教授的集合。一个教授可以在一个以上的部门工作,但是一个部门不能成为一所大学的一部分,部门是在大学外创建的,故大学倒闭,部门还在。

Inheritance(继承)

继承就很好说了,直接是一个类利用extends扩展其父类,而且一个类只能扩展一个父类,但是可以多层扩展。
参考下面的代码:

public class A {
    public void foo() {
        this.bar(); 
    }
    void bar() {
        System.out.println("A.bar");
    }
}
public class B extends A{
    public B() {}
    @Override
    public void foo() { 
        super.foo();
    }
    public void bar() {
        System.out.println("B.bar");
    }
}

可以猜一下下面代码的输出的结果,比较有趣:

B b = new B();
b.foo();

我这里运行的结果是

B.bar

可以看到,在继承中,子类拥有父类所有的方法,而且还可以继续增加父类中没有的方法。

适用于不同场合

总而言之,委派和继承都是为了代码复用,只是方式不同。

  • 委托可以被看作是对象级别的重用机制,而继承是类级别的重用机制。
  • 此外,如果子类只需要复用父类中的一小部分方法,可以不需 要使用继承,而是通过委派机制来实现

猜你喜欢

转载自blog.csdn.net/seriousplus/article/details/80462722
今日推荐