Java - Design with inheritance

当我们使用现成的类来建立新类时,较好的方式是首先选择组合,尤其是不能十分确定应该使用哪一种方式时。组合不会强制我们的程序设计进入继承的层级结构中。而且组合更加灵活,因为它可以动态选择类型(因此也就选择了行为);相反,继承在编译时就需要知道确切类型。

/** *//**
 * Title: 用继承进行设计<br>
 * Description: Dynamically changing the behavior of an object via composition (the "State" design pattern)<br>
 * Company: Augmentum Inc<br>
 * Copyright: 2008 (c) Thinking in Java<br>
 * 
@author Forest He
 * 
@version 1.0
 
*/

package com.augmentum.foresthe;
abstract class Actor ...{
    
public abstract void act();
}

class HappyActor extends Actor ...{
    
public void act() ...{ System.out.println("HappyActor"); }
}

class SadActor extends Actor ...{
    
public void act() ...{ System.out.println("SadActor"); }
}

class Stage ...{
    
private Actor actor = new HappyActor();
    
public void change() ...{ actor = new SadActor(); }
    
public void performPlay() ...{ actor.act(); }
}

public class Transmogrify ...{
    
public static void main(String[] args) ...{
        Stage stage 
= new Stage();
        stage.performPlay();
        stage.change();
        stage.performPlay();
    }

}

Stage对象包含一个对Actor的引用,而Actor被初始化为HappyActor对象;引用在运行时可以与另一个不同的对象重新绑定起来,所以SadActor对象的引用可以在actor中被替代,然后由performPlay()产生的行为也随之改变。这样一来,我们在运行期间获得了动态灵活性(这也称为状态模式,参阅《Thinking in Patterns (with Java))。换句话说,我们不能在运行期间决定继承不同的对象,因为它要求在编译期间完全确定下来。

**一条通用的准则是:“用继承表达行为间的差异,并用组合表达状态上的变化”。在上诉例子中,两者都用到了:通过继承得到了两个不同的类,用于表达act()方法的差异;而Stage通过运用组合使自己的状态发生变化。在这种情况下,这种状态的改变也就产生了行为的改变。

纯继承与扩展

在讨论继承时,采取“纯粹”的方式来创建继承层次结构似乎是最好的方式。也就是说,只有在基类或接口中已经建立的方法才可以在导出类中被覆盖。

这被称作是纯粹的“is-a(是一种)关系,因为一个类的接口已经确定了它应该是什么。这样导出类将具有和基类一样的接口。也可以认为这是一种纯替代,因为导出类可以完全代替基类,而在使用它们时,完全不需要知道关于子类的任何额外信息。也就是说,基类可以接收发送给导出类的任何消息,因为二者有着完全相同的接口。

但是实际应用中我们会发现,扩展接口才是解决特定问题的完美方案。这可以称为“is-like-a”(像一个)关系,因为导出类就像是一个基类——它有着相同的基本接口,但是它还具有由额外方法实现的其他特性。扩展也有缺点:导出类中接口的扩展部分不能被基类访问,因此,一旦我们向上转型,就不能调用那些新方法。

向下转型与运行时类型识别

由于我们在向上转型过程中会丢失具体的类型信息,所以我们可以通过向下转型来获取类型信息。向上转型是安全的,但对于向下转型必须有某种方法来确保其正确性,不致于贸然转到一种错误类型,进而发出该对象无法接受的消息,这样做是极其不安全的。

**java语言中,所有的转型都会得到检查!即使是一次普通的加括弧形式的类型转换,在进入运行期时仍会对其进行检查,以确保它的确是我们希望的那种类型,否则就会返回一个ClassCastException(类转型异常)。这种在运行期间对类型进行检查的行为称做“运行期类型识别”(RTTI)。下面的例子说明RTTI的行为:

/** *//**
 * Title: RTTI(运行时类型识别)<br>
 * Description: Downcasting & Run-Time Type Identification (RTTI)<br>
 * Company: Augmentum Inc<br>
 * Copyright: 2008 (c) Thinking in Java<br>
 * 
@author Forest He
 * 
@version
 1.0
 
*/

package com.augmentum.foresthe;
class Useful ...
{
    
public void f() ...{ System.out.println("Useful.f()"); }

    
public void g() ...{ System.out.println("Useful.g()"); }
}

class MoreUseful extends Useful ...{
    
public void f() ...{ System.out.println("MoreUseful.f()"); }

    
public void g() ...{ System.out.println("MoreUseful.g()"); }
    
public void u() ...{ System.out.println("MoreUseful.u()"); }
    
public void v() ...{ System.out.println("MoreUseful.v()"); }
    
public void w() ...{ System.out.println("MoreUseful.w()"); }
}

public class RTTI ...{
    
public static void main(String[] args) ...
{
        Useful[] x 
= ...new Useful(), new MoreUseful() }// upcasting

        x[0].f();
        x[
1
].g();
        ((MoreUseful) x[
1]).u(); // x[1]可被向下转型为MoreUseful: Downcast/RTTI

        ((MoreUseful) x[0]).u(); // x[0]不能被向下转型为MoreUseful: Exception throw
        MoreUseful a = (MoreUseful) x[0]; // 不能进行强制类型转换,为什么?
    }

}

Useful[]数组中的两个对象都属于Useful类,所以我们可以调用f()g()方法。如果试图调用u()方法,就会返回一条编译时出错消息。

如果想访问MoreUseful对象的扩展接口,就可以尝试进行向下转型。如果所转类型是正确的类型,那么转型成功;否则会返回一个ClassCastException异常。

x[1]可被向下转型为MoreUseful,因为x[1]是从MoreUseful类型转上去的,所以现在还可以下来;x[0]不能被向下转型为MoreUseful,是因为x[0]本来是Useful类型,是MoreUseful的父类。

也就是说向下转型只发生在子类转型到父类后又想做回到自己的时候,不知道我这样理解对不对,另外在什么情况下可以强制类型转换,是在任意类之间吗?如果是这样,为什么上面不行?

***回答:基本类型的强制转换就不说了。引用类型的强制转换有一些先决条件:

首先分清对象和引用。对象在存在于内在中的一个实体,而引用是是告诉程序可以在哪里找到这个实体。由于Java不允许直接操作内存,所以我们对实体没有直观的印象。

然后,搞明白,强制转换是发生在引用上的,不是发生在实体上的。

从子类引用转换到父类引用,是不需要强制转换的。比如,你可以说女人是人,但你不能说人是女人,就是这个道理。

强制转换发生在父类引用到子类引用的情况下,但先决条件是实体的实际类型必须是强制转换后的类型的子类型。比如,一个女人,有这么一个实体,我用“人”来表示她。但进厕所的时候,要分男女,所以我说这个人是女人,可以进女厕所,但如果我说她是男人(比如打扮为男人),可她实际是女人,那么我说他是男人这就是错的,在Java里叫ClassCaseException

阅读更多
个人分类: java
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭