【问题标题】:Code repeating while inheriting继承时代码重复
【发布时间】:2015-02-02 20:10:09
【问题描述】:

我正在从一本名为“Thinking in Java”的书中学习一点 Java,我想尝试一些继承/多态性。但是我遇到了一些我不太喜欢它的代码。就这样吧……

class Person{
    private int hp, attack, speed;
    public status() { System.out.println("HP : " + hp + ", ATT : " + attack + ", SPD : " + speed); }
    public punch(){ System.out.println( " punches"); }
    public getPunched(){ hp -= 5; }
    /* plus standard getters/setters to save up your time */
}
class Bob extends Person{
    private String name = "Bob";
    public Bob(){ setHp(100); setAttack(25); setSpeed(15); }
    public punch(){ System.out.println(name); super.punch(); }
    public getPunched(){ System.out.println(name + " gets punched"); super.getPunched(); }
}
class Jake extends Person{
    private String name = "Jake";
    public Jake(){ setHp(120); setAttack(30); setSpeed(10); }
    public punch(){ System.out.println(name); super.punch(); }
    public getPunched(){ System.out.println(name + " gets punched"); super.getPunched(); }
}

现在一切正常(可能有一些拼写错误,我的版本有点长,所以我在这里重新输入了一个较短的版本)。

我想知道的是,有没有一种方法可以让 Bob 和 Jake 类中的方法 punch() 和 getPunched() 不会重复?更具体地说,我可以将它们都放在 Person 类中,同时保留一个人仍会打印出其子名的函数(“Bob/Jake punches”),还是我必须将它放在其他地方的静态函数中?如果我必须把它放在一个函数中,它应该是什么样子,因为我也在使用保留命令“super”。

我知道这段代码看起来非常琐碎和无用,对此感到抱歉,但它本来就是这样,所以我在编写代码时可以真正看到我在做什么。

谢谢! :)

编辑:在写这篇文章时,我意识到我可以在父类“Person”中创建一个受保护的字符串名称,并通过一个设置器覆盖每个子构造函数中的变量,但是我仍然感兴趣如果可以寻址到子变量,以及如何做到这一点。

【问题讨论】:

    标签: java inheritance methods


    【解决方案1】:

    我会告诉你为什么我不喜欢那个代码。

    基本上,BobJake 不应该是类。它们应该是实例。他们不代表人群,他们应该是个人。当然,您可以说 Bob 类代表世界上所有叫 Bob 的人,但这不是一个真实的用例,甚至不是一个很有启发性的用例。为什么所有 Bob 的血量、速度等都一样?

    一旦您意识到这一点,代码重复的原因就很清楚了 - 由于这种糟糕的设计,您正在编写的东西应该对所有人,或者至少对某些人都是通用的(这将是 Person 的子类) 一次又一次地为每个人。这违背了面向对象编程和一般编程的目的。

    因此,请考虑个人的姓名。所有人都有名字。所以这确实应该是Person 类中的一个字段。它应该像其他所有东西一样有一个 getter 和一个 setter。

    使 Bob 与 Jake 不同的东西应该作为参数传递给构造函数。

    如果你想要一些人共有但其他人不共有的行为,它可能应该在Person 的单个子类中定义。例如PersonWithAnnouncements:

    class PersonWithAnnouncements extends Person {
    
        public PersonWithAnnouncements( String name, int hp, int attack, int speed ) {
            super( name );
            setHp( hp );
            setAttack( attack );
            setSpeed( speed );
        }   
        public getPunched() {
            System.out.println( this.getName() + " gets punched." );
            super.getPunched();
        }
    
        public punch() {
            System.out.println( this.getName() + " punches." );
            super.punch();
        }
    }
    

    然后您可以像这样创建个体 - 实例:

    bob = new PersonWithAnnouncements( "Bob", 100, 25, 15 );
    jake = new PersonWithAnnouncements( "Jake", 120, 30, 10 );
    

    这是一个更好的世界表现——个人获得个人的生命值、速度和名字。行为是类的问题。如果它属于“自己一个班级”的个人——比如ChuckNorris 班级——那么它的行为并不意味着在多个班级中重复。而且您仍然必须在该类中创建一个个体。

    【讨论】:

      【解决方案2】:

      但我仍然感兴趣是否可以寻址到子变量,以及如何做到这一点。

      要实现这种关系,只需在您的父类中使变量name 受保护。然后你可以像私有属性一样在继承的类中访问它。

      另一种方法是提供一个受保护的(或公共的)构造函数,它采用String name,然后将其设置在您的父类中。然后在Person 中实现punch()。不要忘记在您的子类的构造函数中通过super() 调用Persons 构造函数。

      我更喜欢第二种方式,因为它更干净(但这是我个人的看法)。这样您就不需要 setter 方法并使 name 属性成为不可变的,这通常是可取的。

      【讨论】:

      • 哦,对不起,我要说我创建了一个受保护的字符串名称,而不是私有名称。我已经这样做了。我的问题很简单,因为当一个对象声明一个对象(即 Person bob = new Bob(); )时,它基本上会遍历来自孩子的所有信息,然后读取父信息,有没有办法让编译器“反转”它是当然的,所以我可以引用一个子变量/方法。 (与 super 相对,它将针对父变量/方法)...我希望我解释得很好。
      • 其实,没关系,我重读了你说的话。当时间延迟到期时,我会给你一个绿色的勾号。感谢您的快速回复! :)
      • 所以你基本上是想在父类中调用一个孩子的方法?
      • 是的,但我意识到这没有任何意义。当我意识到原始问题的答案并且我太快发布问题时,这只是一个想法。再次抱歉。
      • 如你所说。我没有道理。由于父级不知道有多少或哪些将是继承自它的类。所以它无法知道这些类的属性或方法
      【解决方案3】:

      另一种可能性是在 Person 和 Bob 或 Jake 之间的类层次结构中插入一个类,例如 BobOrJake。

      abstract public class BobOrJake extends Person
      {
          public void setName(String name) {
              this.name = name;
          }
      
          protected String name;
          public void punch(){ System.out.println(name); super.punch(); }
          public void getPunched(){ System.out.println(name + " gets punched"); super.getPunched(); }
      
      }
      

      然后,子类 Bob 将不需要实现 punch() 或 getPunched(),因为它们是在 BobOrJake 类中实现的。

      Bob 变得非常简单(但注意额外的 setName() 来设置 Bob 的名字):

      public class Bob extends BobOrJake {
          public Bob(){ setName("Bob");setHp(100); setAttack(25); setSpeed(15); }
      }
      

      您不必像我那样使 BobOrJake 抽象,但如果您永远不打算实例化 BobOrJake 对象,您可能希望将其保留为抽象。

      【讨论】:

      • 你是对的,是的,它也很好用。但我想保持简单/没有抽象类。不过还是谢谢你的回复! :)
      • 不客气。 “抽象”并没有使它更简单或更复杂。它向你自己和其他程序员表明你不会创建这种对象的实例。在我给出的示例中,BobOrJake 可能应该是抽象的。 Person 也应该是抽象的。
      • 如果每个Person 都可以punch,这只会增加复杂性,因为这也可以由Person 直接实现。但在其他情况下,这可能是一个不错的选择。
      【解决方案4】:

      首先,我不认为BobJake 应该是单独的类。他们所做的根本没有什么不同之处。在内部它们是相同的,Bob/Jake 应该只是同一类 Person 的实例。

      例如,如果我们有 100 个人,除了他们的“统计数据”外,其他人都相同,您是否要创建 100 个单独的班级?如果我们想在运行时创建新人(无论是通过用户输入还是从文件中读取信息)怎么办?你将无法做到。

      相反,内部统计信息(名称、攻击等)应该至少在对象初始化时是可配置的。

      import java.util.*;
      import java.lang.*;
      import java.io.*;
      
      class Person
      {
          protected String _Name;
      
          protected int _Health;
          protected int _Attack;
          protected int _Speed;
      
          public Person(final String name, final int health, final int attack, final int speed)
          {
              _Name   = name;
              _Health = health;
              _Attack = attack;
              _Speed  = speed;
          }
      
          public String getName()
          {
              return _Name;
          }
      
          public void status()
          {
              System.out.println(_Name + ":\nHP : " + _Health + ", ATT : " + _Attack + ", SPD : " + _Speed);
          }
      
          public void punch(final Person target)
          {
              System.out.println(_Name + " punches " + target.getName() + " for " + _Attack);
              target.getPunched(_Attack);
          }
      
          public void getPunched(int damage)
          {
              _Health -= damage;
          }
      }
      
      class Ideone
      {
          public static void main (String[] args) throws java.lang.Exception
          {
              Person bob = new Person("Bob", 100, 25, 15);
              Person joe = new Person("Joe", 120, 30, 10);
      
              bob.status();
              joe.status();
      
              bob.punch(joe);
              joe.punch(bob);
      
              bob.status();
              joe.status();
          }
      }
      

      输出:

      Bob:
      HP : 100, ATT : 25, SPD : 15
      Joe:
      HP : 120, ATT : 30, SPD : 10
      Bob punches Joe for 25
      Joe punches Bob for 30
      Bob:
      HP : 70, ATT : 25, SPD : 15
      Joe:
      HP : 95, ATT : 30, SPD : 10
      

      现在,如果您引入了需要独特逻辑来执行攻击的新角色(例如 WarriorArcher),那么创建子类将是值得的。

      见:

      import java.util.*;
      import java.lang.*;
      import java.io.*;
      
      class Person
      {
          protected String _Name;
      
          protected int _Health;
          protected int _Attack;
          protected int _Speed;
      
          public Person(final String name, final int health, final int attack, final int speed)
          {
              _Name   = name;
              _Health = health;
              _Attack = attack;
              _Speed  = speed;
          }
      
          public String getName()
          {
              return _Name;
          }
      
          public void status()
          {
              System.out.println(_Name + ":\nHP : " + _Health + ", ATT : " + _Attack + ", SPD : " + _Speed);
          }
      
          public void punch(final Person target)
          {
              System.out.println(_Name + " punches " + target.getName() + " for " + _Attack);
              target.getPunched(_Attack);
          }
      
          public void getPunched(int damage)
          {
              _Health -= damage;
          }
      }
      
      class Warrior extends Person
      {
          private int _SwordModifier;
      
          public Warrior(final String name, final int health, final int attack, final int speed)
          {
              super(name, health, attack, speed);
              _SwordModifier = 2;
          }
      
          @Override
          public void punch(Person target)
          {
              int damage = _Attack * _SwordModifier;
      
              System.out.println(_Name + " punches " + target.getName() + " with his sword for " + damage);
              target.getPunched(damage);
          }
      }
      
      class Ideone
      {
          public static void main (String[] args) throws java.lang.Exception
          {
              Person bob = new Person("Bob", 100, 25, 15);
              Person joe = new Warrior("Joe", 120, 30, 10);
      
              bob.status();
              joe.status();
      
              bob.punch(joe);
              joe.punch(bob);
      
              bob.status();
              joe.status();
          }
      }
      

      输出:

      Bob:
      HP : 100, ATT : 25, SPD : 15
      Joe:
      HP : 120, ATT : 30, SPD : 10
      Bob punches Joe for 25
      Joe punches Bob with his sword for 60
      Bob:
      HP : 40, ATT : 25, SPD : 15
      Joe:
      HP : 95, ATT : 30, SPD : 10
      

      【讨论】:

        【解决方案5】:

        免责声明:我的个人解决方案在某种程度上类似于ssell's one:在您的示例中不需要继承。

        但是,另一方面,我们总是从基础开始学习。因此,提出的解决方案与继承/良好实践(实例而不是类)有关,因此我想向您提出一种不同的方法:design patterns。在您的情况下,我认为模板模式可能是一个不错的选择。

        即(示例,可能不完整)

        abstract class Person{
            private int hp, attack, speed;
            public status() { System.out.println("HP : " + hp + ", ATT : " + attack + ", SPD : " + speed); }
            public final punch(){ doPunch(); }
            public final getPunched(){ doGetPunched(); }
            protected abstract void doPunch();
            protected abstract void doGetPunch();
            /* plus standard getters/setters to save up your time */
        }
        
        class Bob extends Person{
            private String name = "Bob";
            public Bob(){ setHp(100); setAttack(25); setSpeed(15); }
            protected doPunch(){ System.out.println(name + " punches");}
            protected doGetPunched(){ System.out.println(name + " gets punched"); setHp(getHp()-5); }
        }
        

        Jake 类也是如此。

        它是如何工作的?通过使主要方法(punchgetPunchedfinal 您可以放心,您希望在这些方法中提供的任何骨架实现都适用于每个扩展类。然后,像Bob 这样的类只需插入您的预期行为。该示例确实是基础知识,可能不是展示此类模式强大功能的最佳示例,但是您可以考虑更复杂的模板(或可插入行为),例如特定情况下的punch 或具有更多效果的getPunched ,其中只有一个由派生类控制。

        【讨论】:

          猜你喜欢
          • 2023-03-04
          • 2012-06-02
          • 2013-07-24
          • 2013-03-10
          • 2020-10-19
          • 1970-01-01
          • 2016-01-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多