Java基础系列之类和接口(二)

2014-11-24 00:41:56 · 作者: · 浏览: 1

对于继承和组合,只需要问自己一点:“子类需要向上转型吗?”,换句话说,只有当父类和子类存在“is-a”的关系的时候,继承才真正实用。更加明确的一点是:一般用来继承的类是相当容易看出它是用来被继承的。

复合是另外一种类的复用方式,这种方式更加的灵活。比如下面这个例子:希望统计set存放过多少次数据?

使用继承

import java.util.HashSet;

public class SetExtendsTest extendsHashSet {

private int i = 0;

private static final long serialVersionUID= 1L;

@Override

public boolean add(E e) {

i++;

return super.add(e);

}

public int getNumber(){

return i;

}

}

这个设计确实是可以满足你的要求,你不需要覆盖addAll方法,因为那个方法是间接的使用了add方法,同样,你也可以使用如下的方法

publicclass AnoterWay extends ForwardingSet {

private int countNumber = 0;

public AnoterWay(Set s) {

super(s);

}

@Override

public boolean add(E e) {

countNumber++;

returnsuper.add(e);

}

public int getNumebr(){

returncountNumber;

}

}

classForwardingSet implements Set {

private final Set s;

public ForwardingSet(Set s){

this.s = s;

}

@Override

public boolean removeIf(Predicate filter) {

returns.removeIf(filter);

}

@Override

public Stream stream() {

returns.stream();

}

}

这里并没有写完所有的方法,其思想便是利用一个类来转发对set的操作请求同时重写add方法,但是这里的set是依据实现了set接口的类,对于任何实现了set接口的类都适合。正是由于AnotherWay将set的操作包装了起来,所以称之为包装器类,也就是设计模式中的包装器模式。这种设计只是一个例子,这里有可能存在过度设计的问题,但是确实,现在的add方法不再依赖具体的类来实现,从而提高了程序的灵活性和健壮性。

总体来说,复合相对于继承更加具有优势(当然这里并不是说复合就比继承好了,具体问题具体分析)

4.用于继承的类

对于专门设计用来继承的类,实际上其设计是十分困难的,正如上面所说,公 有类一旦发布(特别是用于继承的类),其服务就是对他人的承诺。如果需要设计 专门用于继承的类,务必要尽量的考虑得多一些,但是随之而来的便是对这个类的 限制越来越多,这个也是无法避免的,对于成员和方法,你不能暴露得太多也不能 暴露得太少,其类内部的逻辑关系也基本在以后不会发生变化,由于这个类是用于 继承的,所以,类的文档就显得十分十分的重要(对于程序文档有一句格言:好的 API文档应该描述一个给定的方法做了什么工作,而不是描述它是如何做到的)。相 对于这个被用来继承的类,还有一点需要注意的便是-----不要让构造器调用可能被 重写的方法。

public class ConstructorTest {

public ConstructorTest(){

test();

}

public void test(){

System.out.println("A");

}

}

classConstruct extendsConstructorTest{

@Override

public void test() {

System.out.println("B");

}

}

如果你newConstruct对象,那么打印的结果是两个B,这个已经是十分不错 的结果了,由于继承中创建对象的方式,导致子类的对象创建晚于父类对象的创建,但是子类却重写了父类的某个方法,父类的构造器调用了这个方法,由于多态,导致父类在子类的重写方法被调用,但是,子类目前却还没有完成对象的创建,试想,如果重写方法中使用了子类的某个成员,那么这个程序很有可能会出现无法估计的错误。

另一方面,对于那些设计目的并不是用来继承的类来说,就有必要使他们禁止 被子类化!

接口(interface)

Java的接口是Java实现多继承的一个特性,虽然如此说,但是由于接口不提供任何方法的实现,所以在实际的编码过程中可能会存在方法代码的冗余,接口可以继承(叫继承比较合适吧)其他接口(通过implements关键字)。其中的域是public static final的,方法是public的,即使你不这么声明,它是默认的。

《Java编程思想》中有这么一句话(同时也是设计模式的核心准则):“复用代码的第一种方式就是程序员遵循接口编写他们自己的类”,这是面向对象编程的原则之一-----面向接口编程!设计准则中的依赖倒置原则

具体的依赖倒置定义是:

1、高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。

其中的细节说的是类的具体实现,而抽象当然就是抽象类或者接口

在对于依赖倒置原则的描述中“其被称为面向对象设计的标志,用哪种语言来编写程序并不重要,如果编写是考虑的是如何针对抽象编程而不是细节编程,即程序中的依赖关系都是终止于抽象类或者接口,就是面向对象程序设计,反之便是过程化程序设计”。其实在小型的项目中,对于依赖倒置并不会体现到十分的明显,但是对于稍大的项目,涉及到3个以上的子系统,那么不同的子系统会分成各种不同的模块,子系统内部模块的相互依赖,子系统间的相互依赖就会比较的复杂,还不说各个模块内部的类相互之间的依赖,当出现需求变化(可能是由于真实的需求变化或者技术更新再或者开发者水平变高)想修改一下依赖关系(不论是方法级别的修改还是类级别的修改),系统越大,如果设计不好,那么维护将会相比开发付出更多的代价,所以,在大型项目中存在着这样一个名言“设计比实现难!”。

接口设计的另外一个原则便是“接口隔离原则”,其基于依赖倒置原则之上提出的原则。如果类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

\

这个时候就应该将接口I分离成更小的接口,如下图< http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PGltZyBzcmM9"" alt="\">

从而遵循了接口分离原则的基本原则:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

采用接口隔离原则对接口进行约束时,要注意以下几点:

    接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

    相对于类设计准则中的第四点,接口由于是专门用来被实现的,而且必须实现接口中所有规定的方法,所以接口一旦设计出来并被发布出去,那么基本上来说就是一个永久的承诺,所以对于接口的设计,要反复斟酌,这里由于水平有限,所以不能讲到更深的层次。

    有关更多详细的设计方面的艺术,参考设计模式相关的书籍,Java中的接口是Java的核心,但是设计这条路要求更多的经验,所以,如果走到这一步了,基本就是架构师级别了。