【问题标题】:Replacing conditionals with polymorphism in java在java中用多态替换条件
【发布时间】:2019-08-22 15:26:36
【问题描述】:

我最近开始自己练习和学习 Java,我无法理解如何使用多态性来进行更好的编码练习。如何使用多态重构来摆脱以下代码中的 if 和 else 条件?

我有一个父类 Fruit,它有子类 kiwi 和 apple 以及一堆其他水果。 例如:苹果是甜的,猕猴桃是酸的。

我有一个父类 Human,它有子类男孩和女孩。 男孩喜欢甜味,女孩喜欢酸味。 我创建了一个水果对象数组,并使用 if 语句根据水果的味道检查谁喜欢每种水果。

Fruit[] fruit = new Fruit[2];
fruit[0] = new Apple ();
fruit[1] = new Kiwi ();
Boy boy1 = new Boy ();
String boyTaste = boy1.taste;
for (int i = 0; i < fruit.length; i++){
    if (fruit[i].flavor.equals (boyTaste)){
        System.out.println ("Boy likes " + fruit[i].name + " because " + fruit[i].taste ());
    } else {
        System.out.println ("Girl likes " + fruit[i].name +  " because " + fruit[i].taste ());
    }
}

如何通过用多态替换条件来改进以下代码?

【问题讨论】:

  • 在“改进”任何东西之前,您首先必须修复该代码。 flavor 似乎是一个字符串,if (fruit[i].flavor == boyTaste) { 经常会失败。您不应该使用==!= 比较字符串。请改用equals(...)equalsIgnoreCase(...) 方法。了解== 检查两个 对象引用 是否相同,这不是您感兴趣的。另一方面,这些方法检查两个字符串是否以相同的顺序具有相同的字符,这就是这里的重点。
  • 好的,谢谢你的提示。
  • 如果/当您同时实现甜的“红”苹果和酸的“绿”苹果时,多态性可能会派上用场。

标签: java inheritance polymorphism


【解决方案1】:

首先,永远不要使用== 来比较Strings。始终使用.equals(),例如str.equals("hello").

尚不清楚使用专门的多态性/继承可以改进什么。可以提出 Boy boy1 = new Boy(); 应该替换为 Human boy1 = new Boy(); 的论点,但在这种情况下,由于它是一个只使用一次的局部变量,并且在方法中被实例化,它不会产生大量区别。

您可能想要做的事情是在 Human 上创建名为 likesFruit()getMessage() 的方法,如下所示:

public abstract class Human {

    ...

    public boolean likesFruit(Fruit f) {
        return f.getFlavor().equals(getTaste());
    }

    public abstract String getMessage();

    ...
}

getMessage() 将在 Boy 中定义为:

public String getMessage() {
    return "Boy likes %s because ...";  // truncated for brevity
}

以及Girl 的类似实现。

然后在你的代码中,你可以调用:

Human human = new Boy();
Fruit[] fruits = ...

for (Fruit fruit : fruits) {
    if (human.likesFruit(fruit)) System.out.println(String.format(human.getMessage(), fruit.getName(), fruit.getFlavor()));
}

需要注意的几点:

  • 我已将字段引用替换为 getter/setter(例如,fruit.flavor 变为 fruit.getFlavor()。这是一种很好的做法,因为它对调用者隐藏了必要的状态,并允许您在调用 getter 时执行逻辑.

  • Humanabstract。不可能有一个Human 不是BoyGirl(或Man/Woman 等,政治除外),因此实例化一个普通的Human 应该是非法的。

  • 同样,getMessage() 是抽象的,因为实现会根据子类而变化

【讨论】:

  • 谢谢你的提示但是有没有办法不使用IF语句??
  • 为什么要删除if 语句?这种情况适用于任何HumanFruit。长链的if 语句如果硬编码实现细节是不好的,但这并没有这样做。任何强行删除 if 语句的方法都会使您的代码可读性降低,而且不会有太多明显的收益。
【解决方案2】:

我会按以下方式更改代码:

  • 如果可能的话,使用枚举来表示风味 - 将帮助您避免很多编码错误。

  • 用更现代的方法替换 find 方法,可能使用流 API(如果 Java 版本允许)

  • 将代码拆分为功能完善的方法,并将它们分布在各个类中 以有意义的方式

到那时,我会这样写:

我会创建一个这样的水果类,并添加风味枚举作为子类(因为风味是水果的属性)

public class Fruit {
private FLAVOR flavor;

public Fruit( FLAVOR flavor ) {
    this.flavor = flavor;
}

public FLAVOR getFlavor() {
    return this.flavor;
}

public static enum FLAVOR {
    SWEET, 
    SOUR;
}

}

人类类看起来像这样

public class Human {
private FLAVOR flavor;
public Human( FLAVOR flavor ) {
    this.flavor = flavor;
}

public List<Fruit> findLikedFruit( List<Fruit> availableFruits ) {
    List<Fruit> likes = availableFruits.stream().filter( candidate -> Human.this.flavor == candidate.getFlavor() ).collect( Collectors.toList() );
    return likes;
}

}

它基本上包含喜欢的风味,以及一个消耗可用水果列表并返回仅包含喜欢的水果列表的方法

最后,fruit 和 human 都会像这样扩展(仅以 Fruit 为例,但原理适用)

public static class Apple extends Fruit {
    public Apple() {
        super(FLAVOR.SOUR);
    }
}

主类看起来像这样:

public static void main( String[] args ) {
    List<Fruit> fruits = Arrays.asList( new Fruit[] {new Apple(), new Pear(), new Strawberry()});
    List<Fruit> boyLikes = new Boy().findLikedFruit(  fruits );
    //if you wanna output it, you could do like so
    boyLikes.forEach(fruit -> System.out.println( fruit.getFlavor().name() ) );
}

这使得代码更加安全,未来可扩展性更强,并且符合现代 Java 编码实践。

希望有帮助

干杯

【讨论】:

    【解决方案3】:

    修复比较后,为了引入多态性,您需要将 main() 方法中的“条件”移动到类定义中。但在某种程度上,您无法摆脱使用 if/else。但是你的“多态”主方法中的 for 循环可能看起来像这样:

    Human person = new Boy(FlavorPreferences.SWEET)
    for (int i = 0; i < fruit.length; i++) { 
        System.out.println(person.likes(fruit[i]);
    }
    

    另外 - 仅仅因为男孩不喜欢给定的“水果”,并不一定意味着女孩喜欢同样的水果。也许有些男孩和女孩对水果口味的口味是一样的。

    【讨论】:

    • 评论:用户不清楚这里的多态性,因为没有提供喜欢的实现。您的解决方案适用于覆盖的功能,但没有显示并且可能会混淆 OP
    • 由于 OP 正在学习,我遗漏了一些细节以帮助学习过程。
    【解决方案4】:

    你遇到麻烦的地方是你试图将多态性用于一个简单的属性——水果的味道。多态性是针对不同的行为,而不仅仅是不同的值。我试图想出一个水果的例子,但失败了。

    请考虑银行账户。假设有一个定义接口的银行账户的抽象概念,包括一个方法withdrawMoney(double amount)。

    interface Account {  
      boolean withdrawMoney(double amount);
    }
    
    class SimpleAccount implements Account {
      double cash;   // Assume initialized in constructor, not shown.
      boolean withdrawMoney(double amount) {
        if (amount <= cash) {
          cash -= amount;
          return true;
        } else {
          return false;
        }
      }
    }
    class OverdraftProtectedAccount implements Account {
      double cash;
      Account overdraftAccount;  // Assume both initialized in constructor
      boolean withdrawMoney(double amount) {
        if (amount <= cash) {
           cash -= amount;
           return true;
        } else if (overdraftAccount.withdrawMoney(amount - cash)) {
           cash = 0;
           return true;
        } else {
           return false;
        }
      }
    }
    

    当然,这个例子有很多时间问题,只返回一个布尔值,但重点是表明多态性是关于行为,而不是数据。

    【讨论】:

      【解决方案5】:

      让我们编写一个函数来检查某人是否喜欢水果

      public boolean likesThisFruit(Human human, Fruit fruit) {}
      

      你想检查human是否喜欢fruit

      if ( (human instanceof Boy && fruit.taste.equals("sweet"))
          || (human instanceof Girl && fruit.taste.equals("sour"))
      ) {
          return true;
      } else {
          return false;
      }
      

      相当于

      return (human instanceof Boy && fruit.taste.equals("sweet")) 
          || (human instanceof Girl && fruit.taste.equals("sour"));
      

      请注意,您可以使用instanceof 来检查对象是否是给定类型的实例。
      你的最终功能

      public boolean likesThisFruit(Human human, Fruit fruit) {
          return (human instanceof Boy && fruit.taste.equals("sweet")) 
              || (human instanceof Girl && fruit.taste.equals("sour"));
      }
      

      我建议你使用Interfaces:酸甜。并在您的函数中使用instanceof
      比如

      public boolean likesThisFruit(Human human, Fruit fruit) {
          return (human instanceof Boy && fruit instanceof Sweet) 
              || (human instanceof Girl && fruit instanceof Sour);
      }
      

      public class Apple extends Fruit implements Sweet {}
      

      编辑以匹配操作请求

      interface Taste {}
      interface Sour extends Taste {}
      interface Sweet extends Taste {}
      
      public abstract class Human {
          protected final Class taste;
          Human(Class taste) {
              this.taste = taste;
          }
          public boolean likes(Fruit fruit) {
              return fruit instanceof taste;
          } 
      }
      
      public class Boy extends Human {
          Boy(Class taste) {
              super(taste);
          }
      }
      

      我想这里 Fruit 实现了 Sour 或 Sweet 接口

      Boy boy = new Boy(Sweet.class);
      boy.likes(new Apple()); //true
      boy.likes(new Kiwi()); //false if Kiwi implements Sour
      

      在分配味道时,您应该通过设置器检查传递的类是否是味道(使用 instanceof)。

      【讨论】:

      • 那么你的函数 likesFruit 应该在 Human 类中并在子类中被覆盖。每个人都有一个味道(酸或甜),味道是一个传入参数的类。然后您检查水果是否是您的口味的实例。 :)
      • 感谢您的回答,能否分享完整的代码,我一直在尝试您的指示,但仍然出现错误。@IQbrod
      猜你喜欢
      • 2014-08-22
      • 1970-01-01
      • 2021-05-11
      • 1970-01-01
      • 2023-02-23
      • 1970-01-01
      • 1970-01-01
      • 2019-09-08
      • 1970-01-01
      相关资源
      最近更新 更多