23

在java中,围绕一个减少超类功能的子类进行设计的好方法是什么?

例如,考虑具有“See”功能的“Man”类和不应该具有该功能但应该具有“Man”具有的所有其他功能的子类“BlindMan”。

我能想出的唯一解决方案是有一个抽象类“Man”,以及两个子类“SeeingMan”和“BlindMan”,SeeinMan 添加一个函数“See”。

然而,这个解决方案的问题是,如果我现在想添加一个“DeafMan”类 - 它扩展了什么?见人?如果那个人又聋又瞎怎么办?

4

13 回答 13

14

我认为在这种情况下你应该使用组合而不是继承,或者拥有构成人类的各种子类。

虽然我确实理解您的逻辑,但 BaseClass 是一个合同,它保证该类型的所有类都应该遵守这种行为,拥有一个删除父方法的子类是一个很大的不。

虽然你可以抛出各种异常,但我根本不会走这条路。这样想,假设我是一个只需要访问 Human 对象的开发人员,我期望某种行为,突然我调用了一个接口方法并 get..an Exception 只是因为我调用了它??您不应该知道派生类的实现以及何时可以或不能调用它们。

以下是一些解决方案:

让 Human 由 BasicHumanFunctions、VisionSystem 等组成。那么盲人将只有其中的几个。

class Human {
  private BasicHumanFunctions _basicFunctions; //contains breathe funcitonality.
  private VisionSystem _vision; //contains see
}

class BlindMan {
   private BasicHumanFunctions _basicFunctions;
}

使 Human 基类只包含所有人类都希望呼吸等的相同行为,然后创建一个 HealthyHuman 和一个 BlindHuman 等,每个都创建自己的方法。然后,您可以根据需要进一步使用 HealthHuman 和子类。

class Human {
   void breathe() {};
   // other Human basic functions;
} 

class HealthyHuman extends Human {
   void see() {};
   //other healthy human functions
}

class BlindHuman extends Human {
  void useCane() {};
}

对于第二种情况,您仍然可以使用组合来分享行为:

class BlindHuman extends Human {
   private VoiceSubsystem _voice = new VoiceSybsystem();
   void speaker() {  _voice.speaker();}
}
于 2013-06-13T06:33:16.720 回答
8

最重要的原则,是 Lyskov 的替代原则。

一旦你得到它并应用它,你可以随心所欲地构建你的模型。

例如,您可以说盲人仍然可以看到,只是他只能看到黑色(或灰色)的图片。还有一个问题是,如果图片模糊或部分图片,您希望合法盲人仍能看到某些东西。

你也可以说对象可以做的动作是看不见。我们要做的就是观察。观看是观看的(成功)结果。即使有工作的眼睛,你也可能看不到任何东西!盲人仍然可以通过聆听(因此听力,并建立心理“图像”)来“看到”没有眼睛或没有功能的眼睛的东西。

所以,忘掉“看”和“听”吧。使用观察和聆听。并且对此没有任何期望。(广泛的先决条件)。

于 2013-06-13T08:45:31.320 回答
4

Dory 认为组合优于继承是正确的。您的问题/问题与飞鱼问题几乎相同。在这个例子中,我们应该如何处理一个可以飞行游泳的生物?

在此处输入图像描述

将 FlyingFish 放入继承层次结构中会很尴尬。与其试图实现这一点,不如使用组合的力量:

在此处输入图像描述

现在,每个具体类都可以指定它们的行为。根据信息专家的原则,这是这样做的适当地方:信息专家将导致将责任放在具有履行职责所需信息最多的班级身上

注意:很难从上面的截图中看出好处,所以这里再次显示:

好处
- 优先考虑对象组合而不是继承
- 迫使开发人员在编码时对行为做出有意识的决定(而不是在开发人员必须记住覆盖的基类中创建默认实现)。
- 通过抽象不同的行为,它现在可供其他客户端使用。
- 更松散的耦合意味着更容易遵守开放/封闭原则,其中代码应该对扩展开放但对修改关闭。

于 2013-06-13T20:42:24.603 回答
4

接口

定义CommonMan具有共同属性的接口并使用单独的接口对于具有其特定方法的单独子类。然后您可以获得两个接口方法。

例子:

public class SpecificMan extends CommonMan implements specifiManInter

{

//methods of CommonMan

//Methods of SpecificMan 

}

普通人类:

public class CommonMan implements CommonManInter {

 //methods of CommonMan

}
于 2013-06-13T06:42:55.313 回答
1

For example, consider the class "Man", that has the function "See" and the subclass "BlindMan" that shouldn't have that function

You don't get it fully right. Blind man has the function "see" which does nothing (because there's no link between sensors and appropriate brain part, or the sensors are not functioning (return empty list of pixels or image of 0 size).

A good analogy: a Writer has function flush(), but what makes that function for StringWriter?

/**
 * Flush the stream.
 */
public void flush() { 
}

Well, nothing. There's nothing to flush. OK but why the close() throws IOException?

public void close() throws IOException {
}

It's not perfect, but the code works for all types of writers, even those for which flush is not relevant.

But wait, even for the seeing man function see() may return null or empty list. For example, when it's too dark. Just check for null/empty values and the code will work fine with that design.

于 2013-06-13T13:47:41.517 回答
1

在java中,围绕一个减少超类功能的子类进行设计的好方法是什么?

这基本上是在问“继承如何减少功能?”。我能想到的 Java 中的一个例子是Collections#unmodifyableList方法,它说:

返回指定列表的不可修改视图。此方法允许模块为用户提供对内部列表的“只读”访问权限。对返回列表的查询操作“通读”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致UnsupportedOperationException

所以这就是我想要删除某个功能时的目标。但是,请确保它的用法是明确的,例如,Javadoc 中的状态BlindMansee()导致异常。

但是,如果你想“减少”一个以上的特性,也许是独立的,并随着时间改变它们,我宁愿使用变量而不是继承。最后,您没有扩展超类,那么为什么要为此滥用继承呢?你所拥有的是一个具有不同属性的人。

需要记录方法的行为。我可以为您的用例考虑以下类:

public class Human {
    private boolean seeing;

    public void setSeeing(boolean seeing) { ...}
    public boolean isSeeing() { ... }

    /**
     *  Returns what the human sees at the moment, or null
     *  if he/she can't see (seeing is false).
     */
    public String see() {
        if(seeing){
            return "I can see a tree!";
        }else{
            return null;
        }
    }

    // same goes for hearing
}
于 2013-06-13T09:34:23.783 回答
1

在类 Man 中添加hasEyes默认情况下为真,并在默认情况下BlindMan使其为假(或使盲人实例化 Man 之后立即被抢劫,例如 a BlindManFactory)。然后在 Man 的See代码中,如果他没有眼睛,它会自动失败。

于 2013-06-13T06:32:36.030 回答
1

你可以使用布尔值

例如:isSeeing()

并且在您的函数中,您可以在 if else 语句中使用它

于 2013-06-13T06:35:37.197 回答
1

让人类按照他的方式做事,给他任务,而不是命令。

视觉是某些活动的一部分,所以让他独立使用(或不使用)视觉。

例如,告诉他去某个地方旅行。健康的人会使用视觉,盲人会使用其他感官并且效率会降低(除非它处于完全黑暗中)。

如果给定任务需要视力(例如警卫塔上的警卫),您需要询问他们的视力有多好(没有视力不佳的盲人)。

有时你想问他们看到了什么(而不在乎他们听到什么等)然后只是询问并期望盲人(和其他视障者)什么都看不到。

如果您希望人类成为被动对象并由其他对象来处理它们,一种尚未提及的方法是:查看游戏编程中使用的实体组件编程。它基本上是以通用方式使用组合。实体仅保存身份,组件保存有关实体某些方面的数据(例如,视力可能是屈光度数或更有用的统计信息),系统处理具有给定系统所需的组件组合的实体集。Sight 系统可能需要Sight 组件并创建包含当前实体现在看到的实体的组件 seenEntities。其他系统可以合并所有感官的结果并用它来执行一些有用的事情。

于 2013-06-13T11:15:12.143 回答
0

像这样的东西怎么样:

 public class Man {

    Ears ears;

    Eyes eyes;

    }

    public class Eyes {

    public boolean canSee()

    }

    public class Ears {

    public boolean canListen()
    }

那么逻辑就像......

if(man.getEyes().canSee()){ //not blind

}
else
{
//blind logic
}

if(man.getEars().canListen())
{
//Not Deaf
}
于 2013-06-13T06:45:48.167 回答
0

如果您不希望调用该函数,则可以抛出异常。

eG 在您的场景中:InablityException

像这样:

public interface Man {

    public Object see() throws InabilityException;
    public Object hear() throws InabilityException;

}

public class BlindMan implements Man {
    public Object see() throws InabilityException {
       throw new InabilityException("blind people don't see");
    }

    public Object hear() {
       return "what I hear";
    }
}

DeafMan 反之亦然。

class如果 Man 是 a而不是a ,同样的事情也有效interface

您可以(并且应该)将其与@Pataschu 答案https://stackoverflow.com/a/17080626/881272throw结合起来,并且ExceptionifhasWorkingEyes()返回 false。

于 2013-06-13T06:30:17.227 回答
0

接口

我将通过接口定义这两种行为(BlindMan 和 SeeingMan),只公开定义类型的调用(BlindMan 和 SeeingMan)。您不应该真正关心下面的实际实例类型是什么。

抽象父类

现在,您可以将这些接口与具有所有默认行为的“父”抽象类结合起来(盲和看见;如果您也选择的话)

将两者结合起来

public interface BlindMan {
         public void useBlindStick();
    }

   public interface SeeingMan {
        public void see();
   }


  public abstract class Man {
       public void useBlindStick() {
           //work
       }

       public void see() {
           //work
       }
  }


  public class SomeBlindMan extends Man implements BlindMan {
        //bla bla
  }


 public class SeeingMan extends Man implements SeeingMan {
       //bla bla
 }

希望能帮助到你

于 2013-06-13T06:41:07.380 回答
0

要实现一项功能,请使用interface

要扩展或重新定义功能,请通过扩展基类来使用子类

如果您想在运行时动态添加行为,请实现装饰器模式。

相关 SE 帖子:

实现与扩展:何时使用?有什么不同?

何时使用装饰器模式?

于 2016-12-14T12:30:10.233 回答