我们说我有一个班级成员如下:
public class Person {
String name;
int age;
}
和许多子类,如
public class Student extends Person {
// extra fields and methods
}
public class Teacher extends Person {
// extra fields and methods
}
现在,考虑到对于某些应用程序,我需要为每个人实例分配一个整数id,但我不想扩展Person
接口以在那里添加getId()和一个字段到持有身份证。一个简单的解决方案是使用类似的包装器:
public class PersonWrapper extends Person {
public PersonWrapper(Person p, int id) { // assign the id and other fields }
public int getId() { return id; }
}
这样客户端代码仍然可以使用Person接口,并且可以使用包装人员 作为一个人对待。 这种方法的问题是PersonWrapper是Person的子类,而不是教师或学生,这样的代码不会起作用:
Teacher t = new PersonWrapper(teacher, 1);
t.giveGrade();
当然,可以为Person
的所有子类创建具体的包装类型,但我想知道是否有更优雅的解决方案。理想的解决方案是这样的:
public class PersonWrapper<T extends Person> extends T
这样任何PersonWrapper都是它包装类型的子类,但在Java和I中它是不可能的 怀疑这种定义可能无法用任何语言表达。
在任何情况下,如何在不更改与person及其子类一起使用的客户端代码的情况下将id分配给子类,而不为每个子类创建具体的包装器?
答案 0 :(得分:4)
包装器不一定需要扩展到它包装的类。所以,只需使用PersonWrapper<T extends Person>
:
public class PersonWrapper<T extends Person> {
T person;
int id;
public PersonWrapper(T person, int id) {
this.person = person;
this.id = id;
}
//getters and setters...
}
此外,类只能在编译时从另一个类扩展,因此PersonWrapper
不能同时从Student
和Teacher
扩展,这使得不可能你正在寻找的东西。
唯一的解决方案是使用像cglib这样的库动态创建代理类。例如,当需要动态添加功能时,Spring会为类创建代理,例如为方法或全班添加事务管理。
答案 1 :(得分:1)
此问题的常见解决方案是将Person
设为interface
。
interface Person {
public String getName();
public int getAge();
}
class ActualPerson implements Person {
private final String name;
private final int age;
ActualPerson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String getName() {
return name;
}
@Override
public int getAge() {
return age;
}
}
class PersonWithId implements Person {
private final Person person;
private final int id;
PersonWithId(Person person, int id) {
this.person = person;
this.id = id;
}
@Override
public String getName() {
return person.getName();
}
@Override
public int getAge() {
return person.getAge();
}
}
不要害怕大量的代码 - 与你后悔的时间相比,你编写代码的时间是微不足道的。 Old Curmudgeon 2014
答案 2 :(得分:0)
你是对的,你不能做你想做的事。假设您不能将具体类更改为Student extends Person implements Identifiable
,那么最好的办法是将您的包装器视为包装器,并使用一个返回其不同元素的getter:
public class Wrapper<T> {
private final T item;
private final int id;
...
public int getId() { return id }
public T getItem() { return item; }
}
这是一个很难使用的位,因为您必须执行wrapper.getItem().giveGrade()
而不是wrapper.giveGrade()
之类的操作。这也意味着你无法将包装器推入List<Teacher>
,然后将其向下转发到TeacherWrapper
- 但这有点脆弱,并且通常有更好的方法来实现你想要的。对于大多数情况,这种“纯粹”的包装方法可以满足您的需求。
请注意,我甚至没有T extends Person
。如果包装类不需要使用任何Person
方法,那么通过人为限制通用就无法获得多少收益。呼叫站点都将受到限制。 一个的区别在于,如果呼叫网站有Wrapper<?>
,那么我的代码只会让您将该项目作为Object
,而限制性更强T extends Person
将允许您将该项目设为Person
。
答案 3 :(得分:0)
我希望我没有错过任何东西,但在我看来,包装模式解决了你的问题:
public class Person implements IPerson{
String name;
int age;
public static void main(String[] args)
{
Teacher teacherWithID = new Teacher(new PersonWithID(new Person()));
Teacher teacherWithoutID = new Teacher(new Person());
}
}
interface IPerson{}
class Teacher implements IPerson{
public Teacher(IPerson personToBeWrapped){}
}
class Student implements IPerson{
public Student(IPerson personToBeWrapped){}
}
class PersonWithID implements IPerson{
public PersonWithID(IPerson personToBeWrapped){}
}
你的变量的类型应该是最后一个包装器。
包装器模式可以被认为是一种机制,允许您在运行时“扩展”类。由于这个原因,它也被称为装饰器。您的代码中有竞争继承机制。 (内置的一个和模式)结果是你不能输入你的变量。 如果你只使用模式,它就可以工作。