【问题标题】:Java wrapper class subclass of concrete type具体类型的Java包装类子类
【发布时间】:2014-12-17 19:17:18
【问题描述】:

假设我有一个班级人如下:

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() 和一个保存 id 的字段。一个简单的解决方案是使用如下包装器:

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 或 Student,这样的代码不会起作用:

Teacher t = new PersonWrapper(teacher, 1);
t.giveGrade();

当然,可以为Person 的所有子类创建具体的包装器类型,但我想知道是否有更优雅的解决方案。理想的解决方案是这样的:

public class PersonWrapper<T extends Person> extends T

所以任何 PersonWrapper 都是它包装的类型的子类,但在 Java 中是不可能的,我 怀疑这样的定义可能无法用任何语言进行。

在任何情况下,如何在不更改与 person 及其子类一起使用的客户端代码的情况下为子类分配 id,而不为每个子类创建具体的包装器?

【问题讨论】:

    标签: java subclass wrapper


    【解决方案1】:

    包装器不一定需要扩展到它所包装的类。所以,只需使用PersonWrapper&lt;T extends Person&gt;

    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不可能同时从StudentTeacher扩展,这使得不可能 你在找什么。

    唯一的解决方案是使用像cglib 这样的库动态创建代理类。例如,当需要向类动态添加功能时,Spring 会为类创建代理,例如为方法或整个类添加事务管理。

    【讨论】:

    • 但我不希望我的客户每次看到它时都打开它。 PersonWrapper 不是 Person 的子类型!
    • @Ali 您与第一个要求相矛盾:考虑对于某些应用程序,我需要为每个人实例分配一个整数 id,但我无法扩展 Person 接口 假设你的包装器不能从PersonWrapper 扩展。
    【解决方案2】:

    此问题的常见解决方案是将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();
        }
    }
    

    不要害怕编写大量代码 - 与您后悔当初没有正确编写代码所花费的时间相比,您编写代码所花费的时间是微不足道的。 2014 年老顽童

    【讨论】:

      【解决方案3】:

      你是对的,你不能做你想做的事。假设您无法将具体类更改为 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&lt;Teacher&gt;,然后再将其向下转换为TeacherWrapper——但这有点脆弱,而且通常有更好的方法来完成你想要的。在大多数情况下,这种“纯”包装方法可以满足您的需求。

      请注意,我什至没有T extends Person。如果包装类不需要使用任何Person 方法,则人为限制泛型不会有太多好处。无论哪种方式,呼叫站点都将受到限制。 一个的不同之处在于,如果调用站点有Wrapper&lt;?&gt;,那么我的代码只会让您以Object 的形式获取项目,而更严格的T extends Person 将让您得到它项目为Person

      【讨论】:

        【解决方案4】:

        我希望我没有遗漏什么,但在我看来,包装模式解决了您的问题:

        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){}
        }
        

        无论你的变量是什么类型,都应该是最后一个包装器。

        包装器模式可以被认为是一种允许您在运行时“扩展”类的机制。出于这个原因,它也被称为装饰器。您的代码中有相互竞争的继承机制。 (内置和模式)结果是您无法键入变量。 如果您只使用该模式,它就可以工作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-03-16
          • 1970-01-01
          • 2016-10-03
          • 1970-01-01
          • 1970-01-01
          • 2022-08-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多