【问题标题】:Giving extending classes a specific behaviour?给扩展类一个特定的行为?
【发布时间】:2012-10-20 03:12:30
【问题描述】:

我怎样才能有一个类Fruit的多个扩展,并根据对象是什么类/类型,在这个水果上执行相同的方法但功能不同?

示例:我正在使用FruitManager 向水果店添加新的Fruit fruit = new Apple();。如果这个水果是苹果,我当然想把它添加到苹果列表中。香蕉清单也是如此。 现在,如果我有 10 种水果,我不想创建像 addBanana()addApple() 等 10 个函数。 而且我也不想为了获得正确的水果清单而使用混乱的 if-else 语句。

我能否仅根据我添加的对象类型来获取水果列表?

class Fruit;
class Apple extends Fruit;
class Banana extends Fruit;

class FruitStore {
    List<Fruit> apples = new ArrayList<Apple>();
    List<Fruit> bananas = nwe ArrayList<Banana>();
}

class FruitManager {
    FruitStore store;

    //called from somewhere with Fruit fruit = new Apple();
    addFruit(Fruit fruit) {
        //how could things like this be done in one statement?
        store.<get list apples or bananas>.add(fruit);
    }
}

【问题讨论】:

  • 您能解释一下将它们存储在单独列表中的必要性吗?

标签: java


【解决方案1】:

把我的拼凑变成一个答案。考虑使用HashMap&lt;Class&lt;? extends Fruit&gt;, List&lt;Fruit&gt;&gt; 的HashMap。像这样的:

class FruitStore {
   private Map<Class<? extends Fruit>, List<Fruit>> fruitMap = 
         new HashMap<Class<? extends Fruit>, List<Fruit>>();

   public void addFruit(Fruit fruit) {
      Class<? extends Fruit> fruitClass = fruit.getClass();
      List<Fruit> fruitList = fruitMap.get(fruitClass);
      if (fruitList == null) {
         fruitList = new ArrayList<Fruit>();
         fruitMap.put(fruitClass, fruitList);
      }
      fruitList.add(fruit);
   }

   public void displayStore() {
      for (List<Fruit> fruitList : fruitMap.values()) {
         System.out.println(fruitList);
      }
   }
}

我对此进行了测试:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FruitManager {
   public static void main(String[] args) {
      FruitStore fruitStore = new FruitStore();
      for (int i = 0; i < 20; i++) {
         if (i % 3 == 0) {
            fruitStore.addFruit(new Banana());
         } else {
            fruitStore.addFruit(new Apple());
         }
      }

      fruitStore.displayStore();
   }
}

abstract class Fruit {
   public abstract String getName();

   @Override
   public String toString() {
      return getName();
   }
}

class Apple extends Fruit {

   private static final String NAME = "Apple";

   @Override
   public String getName() {
      return NAME;
   }

}


class Banana extends Fruit {

   private static final String NAME = "Banana";

   @Override
   public String getName() {
      return NAME;
   }

}

class FruitStore {
   private Map<Class<? extends Fruit>, List<Fruit>> fruitMap = 
         new HashMap<Class<? extends Fruit>, List<Fruit>>();

   public void addFruit(Fruit fruit) {
      Class<? extends Fruit> fruitClass = fruit.getClass();
      List<Fruit> fruitList = fruitMap.get(fruitClass);
      if (fruitList == null) {
         fruitList = new ArrayList<Fruit>();
      }
      fruitList.add(fruit);
      fruitMap.put(fruitClass, fruitList);
   }

   public void displayStore() {
      for (List<Fruit> fruitList : fruitMap.values()) {
         System.out.println(fruitList);
      }
   }
}

【讨论】:

  • 希望其他 5 位支持您的评论的人也支持此答案,因为这是实现 OP 想要的最佳方式。
  • 我建议的唯一更改是将 map.put 方法放在 if 条件中,从而消除多个冗余 map.put 调用 - 您只需立即将列表放入映射中一次施工后。
  • @MetroidFan2002:是的,这似乎是一种更好的做事方式。所做的更改,谢谢!
【解决方案2】:
List<Fruit> apples = new ArrayList<Apple>();

首先,这在 Java 中无效,因为水果列表不能指向苹果列表,因为 List&lt;Fruit&gt; 可能包含任何水果,即苹果或香蕉,而 ArrayList&lt;Apple&gt; 只能包含苹果.

其次,当您有这样的继承链时,您通常不会将它们存储在单独的列表中。您可以将它们存储在一个列表中,List&lt;Fruit&gt; 并且对该列表中的每个元素执行的任何操作都将具有多态行为,并且基于 Fruit 的实际类型,行为可能与对应的定义不同类。

因此,您应该重新考虑您认为需要单独存储它们的用例。

【讨论】:

  • 这并没有解决对列表本身进行操作的需要。例如 getAllBananas() 之类的实现。是的,您可以将它们从主列表中删除,但效率非常低。
  • 同意@FoolishSeth,除了计数操作,从行为的角度来看,您想使用instanceof 操作符还是使用多态行为?如果你使用 instanceof 操作符,你将如何处理装饰过的水果,例如一个装饰过的苹果,它的实例化苹果会失败?
  • 我会在类类型上使用 Map 键。虽然在一定程度上取决于预期有多少不同的水果类型。因为您可能不知道,所以始终是一个棘手的设计决策,但没有必要在每种情况下都支持无限的多样性。
  • 这仍然不能解决装饰对象,@FoolishSeth。
  • 如果你选择通过你的装饰器实现来打破istanceof,那么你应该意识到这个决定的后果。
【解决方案3】:

虽然您可以避免使用 addBanana(..)addApple(..) 等,但如果没有几个 if-else 语句,这是不可行的,除非您想在使用反射时损失相当多的程序效率。

public void addFruit(Fruit fruit) {
    if (fruit instanceof Apple) {
        store.apples.add(fruit);
    } else if (fruit instance of Banana) {
        store.bananas.add(fruit);
    } else if ... { }
}

如果你真的想用反射法……这里是,但我不推荐。

public void addFruit(Fruit fruit) {
    try {
        Class<Fruit> clazz = fruit.getClass();
        String clazzName = clazz.getName();
        String listName = clazzName.toLowercase() + "s";
        Class<FruitStore> fsClazz = store.getClass();
        Field listField = fsClazz.getDeclaredField(listName);
        listField.setAccessible(true);
        List<Fruit> list = (List<Fruit>) listField.get(store);
        list.add(fruit);
    } catch (Exception e) {
        // failed to add fruit
        e.printStackTrace();
    }
}

注意:反射方法未经测试,但我认为可能出现的唯一问题是 Field#get 返回对其值的副本的引用而不是对其值的引用,从而导致 list.add(fruit) 添加到不同的列出比班上的那个。

【讨论】:

  • 我想你可以使用 HashMap&lt;Class, ArrayList&lt;Fruit&gt;&gt; 1+ 来解决它
  • @HovercraftFullOfEels 不一定是杂牌。当然,需要处理继承,但这仍然是一个不错的解决方案。您仍然需要准备Map(很多adds),但每次运行时只需准备一次。
  • @HovercraftFullOfEels 这应该是一个答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-23
  • 1970-01-01
相关资源
最近更新 更多