我是Java新手并且在设计问题上苦苦挣扎。我知道使用instanceof
可能表示存在设计缺陷,我理解通常给定的Animal/Dog/Cat
类作为示例,将bark()
和meow()
替换为makenoise()
等。< / p>
我的问题是,如果我需要根据子类的类型调用没有相应方法的方法,那么什么是合理的设计?例如,如果我想要一个新方法biteleash()
,如果该类是Dog
,但如果它是Cat
则什么也不做呢?
我确实考虑在biteleash()
中Animal
没有做任何事情,并在Dog
中覆盖它,但是有很多这样的方法,所以它似乎是一个笨重的解决方案。类似地,如果调用者需要根据它所拥有的子类做一些不同的事情,例如。如果子类是Cat
,则终止? instanceof
在这里可以接受,还是有更好的方式?
public class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void makeNoise() {
System.out.println("Some noise for a generic animal!");
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeNoise() {
System.out.println("Meow");
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeNoise() {
System.out.println("Woof");
}
public void biteLeash() {
System.out.println("Leash snapped!");
}
}
import java.util.Random;
public class CodeExample {
public static void main(String[] args) {
Animal animal = getSomeAnimal();
System.out.println("My pet is called " + animal.getName());
animal.makeNoise();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.biteLeash();
// do lots of other things because animal is a dog
// eg. sign up for puppy training lessons
}
}
private static Animal getSomeAnimal() {
Animal animal;
Random randomGenerator = new Random();
int randomInt = randomGenerator.nextInt(100);
if (randomInt < 50) {
animal = new Dog("Rover");
}
else {
animal = new Cat("Tiddles");
}
return animal;
}
}
答案 0 :(得分:10)
撰写将在这里为您提供帮助,并且在Java中是惯用的。
设计一个名为Leashable
的接口。这是由Dog
实现的,而不是Cat
。
您可以尝试对instanceof
进行引用,以查看它是否由您的特定对象实现,而不是使用Leashable
。
在我看来,你应该继续以类似的方式:建立一个NoisyAnimal
接口。也许只是Noisy
为什么噪音只与动物相关?例如,为Parrot
实施该问题将会产生超出Cat
或Dog
的技术挑战。良好的可维护程序可以隔离复杂性和组合区域,帮助您实现这一目标。
答案 1 :(得分:5)
你不应该使用具体的课程。实例本身不是问题。它存在是有原因的。您应该使用松散耦合的接口,即您的代码不应该依赖于具体的类实现。我建议你尽可能使用接口(即IAnimal而不是Animal)
不应该检查Dog,你应该使用像ILeashable这样的界面(对于名字来说有点荒谬),然后:
public interface ILeashable {
//add other methods which is connected to being on a leash
void biteLeash();
}
class Dog implements ILeashable {...}
也没有一种方法可以做到这一点,有一些模式,即装饰器或依赖倒置,在这种情况下可能对你有帮助。
答案 2 :(得分:1)
您知道,您遇到的这个问题并不是您在现实世界中普遍面临的问题。如果必须在实现接口或抽象基类的某个类上具有特定于实现的逻辑,那么通常是因为在某个更高级别需要获取派生属性。这个伪代码来说明:
interface ISellable {
decimal getPrice();
}
class CaseItem : ISellable {
int numItemsInCase;
decimal pricePerUnit;
decimal getPrice() {
return numItemsInCase*pricePerUnit;
}
}
class IndividualItem : ISellable{
decimal pricePerUnit;
decimal getPrice() {
return pricePerUnit;
}
}
main() {
aCaseItem = new CaseItem { pricePerUnit = 2, numItemsInCase=5 }; //getPrice() returns 10
anIndividualItem = new IndividualItem { pricePerUnit = 5 }; //getPrice() returns 5
List<ISellable> order = new List<ISellable>();
order.Add(aCaseItem);
order.Add(anIndividualItem);
print getOrderTotal(order);
}
function getOrderTotal(List<ISellable> sellableItems) {
return sellableItems.Sum(i => i.getPrice());
}
请注意,我使用界面抽象出项目价格的概念,但是当我实际上在主方法中时,我可以轻松地创建特定类型的实例以便控制这两个班的行为。
然而,当我需要得到价格时,我将这些项目作为ISellable列表引用,这只会暴露他们的&#34; getPrice()&#34;方便我的方法。
就个人而言,我一直认为动物的情况严重缺乏。它没有以合理的方式解释这个概念,也没有提示如何在现实世界中使用它。
答案 3 :(得分:1)
以这种方式思考:什么事件导致Dog
咬住它的皮带?或者换句话说,你有什么动机让它执行这个动作?
在你的例子中实际上没有。事实上,在您的主要方法中,您决定进行检查,如果您随机创建的动物是Dog
,您可以使它做某些狗的事情。这不是现实世界中的代码如何运作。
编写代码时,需要解决一些问题。为了坚持动物的例子,让我们假装你写一个游戏,你有宠物对某些事件作出反应,比如开启真空或吃点东西。仅从这句话中我们就可以创建一个合理的类层次结构:
interface Animal {
void reactToVacuum();
void receiveTreat();
}
class Dog implements Animal {
public void biteLeash() {
System.out.println("Leash snapped!");
}
public void wiggleTail() {
System.out.println("Tail is wiggling!");
}
@Override
public void reactToVacuum() {
biteLeash();
}
@Override
public void receiveTreat() {
wiggleTail();
}
}
正如你所看到的,皮带咬合是为了响应一个事件,即打开真空。
有关更实际的示例,请使用Android的视图层次结构。 View
是屏幕上每个控件的基类,例如一个Button
,一个EditText
等等。 View.onDraw()
是为每个视图定义的,但根据您拥有的视图,会发生不同的事情。例如EditText
会执行类似drawCursor()
和drawText()
的内容。
正如您所看到的,问题的答案&#34;什么事件导致EditText绘制光标&#34;是&#34;需要在屏幕上绘制&#34;。不需要instanceof
或条件检查。
答案 4 :(得分:0)
如您的示例所示 - 某些子类可以有不同的方法
这是您必须使用instanceof
的典型情况。
答案 5 :(得分:0)
如果您的子类已修复,那么您可以使用animalType
来避免instanceof。
我更喜欢你可以使用接口来检查该子类是否可以使用该功能,但是对于大量的子类依赖方法它是不可行的。 (由user2710256
给出的解决方案。)
Enum Type {
DOG,CAT ;
}
public abstract class Animal {
String name;
Type type;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void makeNoise() {
System.out.println("Some noise for a generic animal!");
}
abstract Type getType();
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeNoise() {
System.out.println("Meow");
}
public Type getType() {
return Type.CAT;
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeNoise() {
System.out.println("Woof");
}
public void biteLeash() {
System.out.println("Leash snapped!");
}
public Type getType(){
return Type.DOG;
}
}
import java.util.Random;
public class CodeExample {
public static void main(String[] args) {
Animal animal = getSomeAnimal();
System.out.println("My pet is called " + animal.getName());
animal.makeNoise();
switch(animal.getType())
{
case DOG:
{
Dog dog = (Dog) animal;
dog.biteLeash();
// do lots of other things because animal is a dog
// eg. sign up for puppy training lessons
}
case CAT:
{
// do cat stuff
}
default:
throw new Exception("Invalid Animal");
}
}
private static Animal getSomeAnimal() {
Animal animal;
Random randomGenerator = new Random();
int randomInt = randomGenerator.nextInt(100);
if (randomInt < 50) {
animal = new Dog("Rover");
}
else {
animal = new Cat("Tiddles");
}
return animal;
}
}
答案 6 :(得分:0)
当然biteleash()
中有Cat
没有意义,因为你不会用皮带牵着猫。因此Animal
不应该biteleash()
。也许您应该在playWith()
中添加Animal
之类的方法,其中Dog
会biteleash()
。我想说的是你可能需要更一般。您不应该关心手动拨打biteleash()
是否是狗。向动物园添加Ferret
需要添加动物为Ferret
的可能性,因为它们也可以biteleash()
。
也许是一种方法shouldTerminate()
,它会在true
中返回Cat
。您可能希望暂时终止Tortoise
。
通常,人们认为这是不好的做法,如果添加新的子类需要使用此类更改代码。
答案 7 :(得分:0)
如果可以的话,这是一种非常不切实际的方法,我不确定你的目标是什么,但是实例会很乏味,你最终得到的是高耦合,这是不可取的。做OOD。 这就是我要做的事情:
首先,摆脱类Animal并使其成为如下界面
public interface Animal {
public String getName();
public void makeNoise();
public void performAggression();
}
然后在狗:
public class Dog implements Animal {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void makeNoise() {
System.out.println("Woof");
}
@Override
public void performAggression(){
biteLeash();
}
private void biteLeash() {
System.out.println("Leash snapped!");
}
}
最后,猫:
public class Cat implements Animal {
private String name;
public cat(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void makeNoise() {
System.out.println("Meow");
}
@Override
public void performAggression(){
hiss();
}
private void hiss() {
System.out.println("Hisssssss");
}
}
这样你就可以在主要课程中做到如下:
public class test{
public test(){
dog = new Dog("King");
cat = new Cat("MissPrincess");
}
public void performAggression(Animal animal){
animal.performAggression();
}
你得到的奖励是,你可以将你想要的任何类传递给一个方法,只要它们实现相同的接口,如上面方法performAggression(Animal animal)
中的测试类所示
所有测试类需要知道的是那3个方法,其他所有方法都可以在相应的类中内部完成,而测试类不需要了解它,因此&#34; private&#34; biteLeash()
和hiss()
方法的可见性。
您最终会得到一个非常低的耦合,以后可以轻松编辑。
通过这样做,你也可以有效地实现高凝聚力,因为你不必长时间参与/ ifelse / ifelse ......(等等)来确定你正在处理什么类的课程。 / p>
答案 8 :(得分:0)
运营商instanceof
只有在应用于接口时才能很好地扩展。原因是接口将您的域分为两部分:一些对象是实现接口的类的实例,而其他对象则不是。引入新类很可能无法打破依赖if (x instanceof ISomething)
的当前算法,因为新类将实现ISomething
或不实现if
。如果是这样,x
正文应该ISomething
投射到if
并充分利用它;如果没有,那么x
的主体可能对instanceof
不感兴趣。
这与Seal
应用于类的方法不同。例如,海豹会吠叫,但它们不是狗:你不能从Dog
继承instanceof
或反之。因此,正如您所暗示的那样,您知道如果您使用x
来检查是否可以if (x instanceof Dog) {
((Dog)x).bark();
}
else if (x instanceof Seal) {
((Seal)x).bark();
}
吠叫,那么您最终可能会遭受这样的可憎行为:
ICanBark
解决方案是使用接口if (x instanceof ICanBark) {
((ICanBark)x).bark();
}
并对其进行测试:
ICanMeow
如果添加更多动物,无论它们是否可以吠叫,这都可以很好地扩展。但是如果你想增加更多的噪音,它就不能很好地扩展,因为你可能想要引入更多的接口,比如ICanBark
等。
解决方案是避免处于这种情况; ICanMeow
和IMakeNoise
显然是相同的行为Dog
,因此Cat
,Seal
和instanceof
实施了单一界面。
在多态性的背景下,我不喜欢动物/狗/猫/噪音的例子。相反,我将向您展示我之前正在研究的组件系统的真实世界示例。在组件系统中,组件安装在实体上,以便为其提供其他行为。例如,视频游戏中的怪物是安装了某些组件的实体(这些东西的灵感来自Unity 5引擎):
可能实现这些行为的四个类可能是:
如果你有一个实体集合,那么如果你为接口for (Entity e : entities) {
for (Component c : e.getComponents()) {
if (c instanceof IAudioSource) {
((IAudioSource)c).play();
}
...
if (c instanceof IUserInput) {
((IUserInput)c).poll();
}
}
}
那么它完全可以接受(并且它会很好地扩展):
c instanceof IUserInput
由于怪物不受玩家控制,instanceof
失败,每个人都很开心。使用ICanBark/ICanMeow
岩石和IAudioSource/IRenderer/IMovementAI/ILiving
糟透了的原因是因为ICanBark/ICanMeow
是怪物意味着四个完全不相关的方面,而IMakeNoise
是两个表现形式相同方面(并且应该通过将它们合并到I did consider having biteleash() in Animal which does nothing, and overriding it in Dog
)来处理。
修改强>
text
这是一个笨重的解决方案,我相信它只能出于性能原因。如果我没弄错的话,Swing for Java会偶尔使用这种模式。