Java,从子类转换为超类的对象是否可能?

8

我有两个类:Student和Tutor。Tutor基本上是一个有facultyID的学生(Tutor扩展了Student)。一旦他的合同结束,他就会回到作为普通学生的身份。那么我能否以某种方式将他转换回之前的学生身份?


根据你的简短描述,也许你不应该使用继承来建模这种关系。你可以考虑创建一个Tutor包装器,在构造时接受一个Student实例。当合同完成后,你可以让对包装器的引用超出作用域,并继续直接使用Student实例进行工作。 - Mike Deck
2
你能提供一些更多的背景信息吗?在我看来,这取决于你想要实现什么。也许添加一个 boolean isTutor; 成员或者简单地保持一个既是学生又是导师的名单就足够了。没有必要仅仅因为你可以而用面向对象的模式来建模所有东西。 - kritzikratzi
@kritzikratzi,我喜欢你的评论“不必将一切都用面向对象建模...只是因为你可以”。完全同意! - corsiKa
8个回答

5
你真正想要做的是使用组合而非继承。将所有对象保持为 Student 类型,然后在需要时临时分配 TutorRole 的行为给每个 Student 实例。
通过这种设计,你的 Student 类将包含一个类型为 TutorRole 的属性(成员变量),你可以在运行时添加或删除它。添加一个 isTutor() 方法将允许您以清晰简洁的方式确定学生是否为导师。 TutorRole 类将封装作为导师的行为(即方法)。
/*
 * The TutorRole can be set at runtime
 */
public class Student {

    private String facultyId;

    private TutorRole tutorRole = null;

    public boolean isTutor() {
        return !(tutorRole == null);
    }

    public void doTutorStuff() {
        if(isTutor()) {
            tutorRole.doTutorStuff();
        }
        else {
            throw new NotTutorException();
        }
    }

    public void setTutorRole(TutorRole tutorRole) {
        this.tutorRole = tutorRole;
    }
}

/*
 * Ideally this class should implement a generic interface, but I'll keep this simple
 */
public class TutorRole {

    public void doTutorStuff() {
        // implementation here
    }
}

/*
 * Now let's use our classes...
 */
Student st = new Student(); // not a tutor
st.setTutorRole(new TutorRole()); // now a tutor
if(st.isTutor()) {
    st.doTutorStuff();
}
st.setTutorRole(null); // not a tutor anymore

一种替代方案是让一个 Tutor 类包含对一个 Student 对象的引用,但这取决于您将如何与学生和导师对象进行交互,以及您想要编写哪种代码。

4
我认为,这个表达了封装和接口编程的思想。
这个怎么样:
interface IStudent
{
  String getName();
  int getStudentId();
}

interface IFacultyMember
{
  int getFacultyId( );
}

class Student
  implements IStudent
{
  String name;
  int id;

  public String getName( ) { return name; }
  public int getStudentId( ) { return id; }
}

class Tutor
  implements IStudent, IFacultyMember
{
  Student student;
  int facultyId;

  public Tutor ( Student student, int facultyId )
  {
    this.student = student;
    this.facultyId = facultyId;
  }

  public String getName( ) { return student.getName( ); }
  public int getStudentId( ) { return student.getStudentId( ); }
  public int getFacultyId( ) { return facultyId; };
}

这样,即使学生成为导师,它仍然保持学生身份。当导师任期结束后,您只需回收导师记录即可。另一方面,学生的记录仍将在中央服务中保留。

3
一旦创建某种类型的实例(例如,Tutor),该实例的运行时类型就已确定,无法再更改。
一些替代方案:
  • Student 提供一个构造函数,该函数接受另一个 Student 实例并复制相关字段。这就是所谓的复制构造函数。这样,您可以轻松地基于您的 Tutor 创建一个新的 Student 实例,然后使用它。
  • 如果保留实际实例比创建副本重要(也许您在各个位置都保留了引用),最好将角色与类分开。创建一些 FacultyMember 类或其他类,并将 StudentTutor 转换为角色。然后您可以稍后更改 FacultyMember 的角色。

2
您可以将Tutor强制转换为Student,这样在编译时您的代码会将其视为Student处理,但对象仍将保持为Tutor,因此调用任何重写的方法将导致调用Tutor类的版本。
您想要的“转换”的唯一方法是创建一个新的Student对象,并赋予它Tutor拥有的所有属性。
由于您发现需要进行此转换,您可能需要重新考虑类结构。例如,也许学生和导师真正只是人员,每个人员可以担任学生或教师的角色。

请您详细说明一下吗?我不确定该如何做。 - vedran
@vedran:如果您想编辑您的答案,提供一些当前代码和更全面的解释,说明您试图将一个人从一种类型转换为另一种类型的目的,我可以给您一些更好的反馈。 - StriplingWarrior

1

在Java中,您无法更改对象类型。可能实现您的方法最简单的方式是将Tutor中的所有参数克隆到一个新的Student对象中。


1
你可以在Tutor上编写一个像TutorToStudent()这样的方法,并从Tutor创建一个新的Student。如果你进行强制类型转换,最终得到的对象仍然是Tutor。
在这种情况下,你可能还考虑放弃继承,只需设置一个标志来指示该学生是否为导师。

但是如果你有一组学生,它仍然会保留旧的导师,而不是新的普通学生。 - corsiKa
@glowcoder:没错,但这适用于大多数仍保持继承层次结构并尝试将导师转化为学生的解决方案。像任何解决方案一样,它不能解决所有情况,显然需要额外编码来处理您提到的情况。 - Ian Dallas
是的,对于维护继承层次结构的解决方案来说,这是正确的。但是“优先使用组合而非继承”是一个好的实践。最好拥有一个“学生”角色和一个“导师”角色,并且只需拥有角色(如果使用列表,则为角色列表)的实例即可。 - corsiKa

1
1. 你可以使用拷贝构造函数 Student(Student original) 或者 Student toStudent() 方法创建一个与导师信息完全相同的新学生对象,并且丢弃原来的对象。这是一种快速而不太规范的方法。
2. 不要让Tutor直接扩展Student,将Tutor特定的数据和行为变成装饰器。更好的方法是,将Tutor和Student都作为People对象的装饰器,包含所有人的信息。这是一种正确的方法之一。
3. 将Tutor和Student制作为枚举类型Type并在People对象中持有此字段,这比上面的方法更简单,但可扩展性较差,真正依赖于Tutor和Student之间的差异程度。

1

没有所谓的“将他转换回学生”,他是一位导师,而导师已经是一名学生。

然而,泛化对象的访问可能具有一定的价值,这样您只需使用学生方法。Java 中有几种惯用语可供选择。

1)您可以在代码中专门使用学生 API,但除了导师部分之外。

2)您可以为您的对象编写“toStudent/toTutor”方法,使它们能够返回特定于角色的对象。

//我更喜欢的方式 3)您可以使用接口。将导师和学生都作为接口,并使用接口实现构建您的对象。如果您使用最小接口引用对象,而不是重量级继承模式,那么您的代码在未来可能会更好地扩展。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接