`
songofhawk
  • 浏览: 26447 次
  • 来自: ...
社区版块
存档分类
最新评论

如何减少子类对超类的依赖——一个设计问题

    博客分类:
  • Java
阅读更多

一般来说,根据所谓好莱坞原则,我们不应该在子类中显示调用超类的方法,而是通过重写超类的方法来实现特殊的逻辑,以此来避免循环依赖。不过,调用超类中被重写的同名方法,通常是可以接受的,比如:

Class A
{
  public void go()
  {
     System.out.println("do by A");
  }
}

Class B extends A
{
  @Override
  public void go()
  {
     System.out.println("do by B");
     super.go();
  }
}

 

如果超类要做的事,总在子类之前,或者之后,是没有问题的,而一旦子类do的前后各需要一段公共的代码,这个办法就行不通了。

 

于是我们干脆把一个方法拆成两个,其中一个“大”方法调用另一个“小”方法,由子类去重写那个小方法,这样子类干脆就不用掉父类的方法了,显得更加纯粹:

 

abstract Class A
{
  public void go() //大方法
  {
     prepare()
     going();
     clearup()
  }

  abstract public void going(); //小方法

  protected void prepare()
  {
     System.out.println("prepare by A");
  }

  protected void clearup()
  {
     System.out.println("clearup by A");
  }
}

Class B extends A
{
  @Override 
  public void going() //重写小方法
  {
     System.out.println("do by B");
  }
}

 

问题是,如果Class B还有子类呢,它自己也需要在子类的执行逻辑前后插入一些东西,难道又把doing这个“小”函数拆开?这是不是太复杂了点?再说我都不知道怎么跟下面的“小小”函数起名字了wink 。当然,让子类反过来调Class B的函数更不好,不但违反了前面的“Don't call me”原则,而且本身就很麻烦——每个子类都得写。

 

有没有更好的办法呢?  看来这个问题没写清楚,引起了一些误会,有必要进一步解释一下。首先,我这里并不是想验证或者实践某种模式,确实是一个实际的开发项目遇到了需要权衡的地方。

 

目前我的设计大致是像下面的样子:

 

设计问题类图

 

只所以选用继承结构,是因为子类所代表的几个概念与父类在自然意义上确实是"is a"的关系,并且在子类间是互斥的;而且A/B/C这几个类都很稳定,可能发生的变化主要是B可能会增加子类B3,B4,或者A会增加子类D,E之类的。

 

使用它们的客户程序在获得一个实例之后,一般情况下只会调用它们的go方法,也就是说,不关心具体的实例是属于哪种类型的。当然,有很多方式都可以实现这个需求,而我关心的是,怎样才能让可能新增的B3、B4、D、E这些类实现起来最简单、可靠。所以,最终还是选择了逐步细分函数的方式。

 

代码如下:

abstract Class A
{
  public void go() //大方法
  {
     prepare()
     going();
     clearup()
  }

  abstract public void going(); //小方法

  protected void prepare()
  {
     System.out.println("prepare by A");
  }

  protected void clearup()
  {
     System.out.println("clearup by A");
  }
}

abstract Class B extends A
{
  @Override 
  public void going() //重写小方法
  {
  	 beforeRun();
  	 run(); //小小方法
  	 afterRun();
  }
  
  abstract public void run();
  
  protected void beforeRun()
  {
  	 System.out.println("beforeRun by B");
  }

  protected void afterRun()
  {
  	 System.out.println("beforeRun by B");
  }
}

Class B1 extends B
{
  @Override 
  public void run() //重写小小方法
  {
     System.out.println("go by B1");
  }
}

Class B2 extends B
{
  @Override 
  public void run() //重写小小方法
  {
     System.out.println("go by B2");
  }
}

Class C extends A
{
  @Override 
  public void going() //重写小方法
  {
     System.out.println("go by C");
  }
}

 

 

这样,B在自己的层次,“要求”本类别的Class在go的时候,必须按顺序调用beforeRun和afterRun,新增的B?子类只需实现自己的run方法即可被正确使用;万一真有很特殊的情况,新的子类希望在go的时候不要调用beforeRun或者afterRun,那么用一个空函数覆写它们即可。

 

反之,如果beforeRun/afterRun这样的函数需要B?子类来调用,那么绝大多数子类都要编写调用的代码,也包括将来可能扩充的,这显然造成了一定的代码重复。

分享到:
评论
6 楼 songofhawk 2008-03-28  
To C3PO:

老兄这个观点我不大同意:继承确实是类之间静态关系最强的一种,但如果子类能不去“了解”父类中比较一般的逻辑,而专注与自己的“特殊”逻辑,显然会让整个代码更有弹性,也更清晰。何况,在我这个情景中,如果由子类去调父类的prepare之类的方法,就意味着每个子类都得同样的写法,代码重复不说,对以后可能出现的新的子类如何实现go方法,也提出了额外的限制。
5 楼 songofhawk 2008-03-28  
To 抛出异常的爱:

AOP的方式对于我这个简单需求来说太重了些。

关于匿名类,我没太理解,如何能解决这个问题呢,那个b要定义在什么地方呢?可能是我不够熟悉匿名类的机制吧。

能否帮我讲得再详细一些,谢谢!
4 楼 kebo 2008-03-28  
唉.........你这样用到底是什么目的,可读?可扩展......亦或是为了实现这个模式?
3 楼 songofhawk 2008-03-28  
HRoger:
 
  谢谢你的建议。

  不过,我关心的是如何在一棵继承树中,实现对一个方法调用的按层次“包装”,这是因为例子中的几个类,在概念上是天然符合继承关系的,而且每个层次需要“包装”的内容,也是确定的。

  如果采用decorator模式,就意味我还需要再建立一个decorator类,并且使用该类来“代理”go请求,这样的复杂度似乎比拆分“小”函数还要高;而且,在继承树中当中使用,确实不太方便。

  当然,如果完全抛弃继承结构,而采用decorator来层层“包装”,确实可以得到更好的动态灵活性,但这些灵活性是当前用不到的,同时,丧失了继承带来的清晰和简洁(可以继承的不只go一个方法)。

  所以,目前看来decorator模式还不是更优的选择。
2 楼 抛出异常的爱 2008-03-28  
spring AOP解决方式
或把B匿名
引用
A b =new A(){
   public   going(){}
}
1 楼 HRoger 2008-03-27  
看看装饰者模式吧,调用前添加一些自己的动作

相关推荐

    UML课件——第二章

    - **例子**:如果一个类`Customer`使用了一个类`Address`的实例来存储客户地址信息,则`Customer`对`Address`有一个依赖关系。 2. **类属(Generalization)关系** - **定义**:类属关系也称为泛化或继承关系,...

    12.java学习第十二章——继承extends.pdf

    - **术语定义**:当一个类(B类)继承另一个类(A类)时,A类被称为超类、父类或基类;而B类则被称为子类、派生类或扩展类。通常情况下,人们更倾向于使用“父类”和“子类”这样的术语。 - **示例代码**:`class A ...

    深入浅出设计模式第一章中文

    - “SimUDuck”游戏采用面向对象(OO)设计,其中`Duck`作为一个超类,各种鸭子作为其子类继承自`Duck`。 - `Duck`类包含通用行为,如`quack()`(呱呱叫)和`swim()`(游泳),而`display()`(显示外观)则在每个子类中具体...

    java初涉设计模式

    最初的设计采用了面向对象的技术,通过定义一个`Duck`超类以及一系列继承自该超类的具体鸭子子类来实现这一目标。 - **Duck** 超类定义了所有鸭子共有的行为,如`quack()`和`swim()`。 - 每种鸭子(如`MallardDuck`...

    数据库系统原理——ER模型与关系模型.pdf

    数据库系统可以视为软件的一个子类,它是软件超类的实例化。在这个关系中,软件通常被视为强实体,而数据库系统则相对较弱,依赖于软件的存在。在数据库设计阶段,概念模型,即ER模型,转化为关系模型是核心任务。这...

    继承与多态PPT学习教案.pptx

    1. **继承的定义**:继承机制允许一个类(子类)基于另一个已存在的类(父类)进行定义,这样子类就可以直接继承父类的属性和方法,减少了代码重复,增强了代码的复用性。在某些文献中,父类也被称为超类或基类。 2...

    Enterprise Architect学习笔记

    它表示了一般与特殊之间的关系,即一个类(子类)可以从另一个类(超类)继承属性和行为。在 UML 图中,泛化关系用实线加上三角箭头表示,箭头指向超类。这种关系允许子类扩展或重写超类的行为,同时保留其所有属性...

    继承与多态性 实验五实验报告(面向对象程序设计).docx

    在面向对象编程中,继承是一种重要的机制,它允许我们定义一个类(称为子类或派生类)来继承另一个类(称为父类或基类)的属性和方法。这种机制可以有效地复用代码,并支持类之间的共享行为。继承的基本概念包括: ...

    Java语言程序设计(一)04747汇总(2016——2017全)推荐文档.doc

    继承:面向对象编程的一个核心概念,允许创建新类来继承现有类的特性。 **4. 数组创建** - **选项分析**: - A. `int myA[5]={1,2,3,4,5);`:语法错误,数组声明和初始化的方式不正确。 - B. `int myA[]=new...

    UML 经典教程 统一建模语言

    - **依赖**(Dependency):依赖关系表示一个模型元素(依赖方)对另一个模型元素(提供方)的依赖性。例如,一个`CourseSchedule`类可能依赖于`Course`类来进行课程的添加和删除操作。 - **泛化**(Generalization):...

    UML类关系之JAVA代码实现

    2. **泛化(Generalization)**:泛化是类的继承关系,一个类(子类)继承另一个类(超类)。在Java中,这通过`extends`关键字实现。例如: ```java public class A extends B {} ``` 泛化关系在UML中用一个带空心箭头...

    04735数据库系统原理200710.doc

    5. 如果一个实体X的存在依赖于另一个实体Y的存在,且X的主键部分或全部来自Y,则X被称为弱实体(C.弱实体)。 6. 插入异常(A.不该插入的数据被插入)是关系规范化中的一个问题,指的是在数据库中不应该出现的插入...

    31天重构速成

    箭头抗模式(Arrowhead)是指在一个类中过度依赖另一个类的情况。重构时,应该尽可能减少这种依赖,提高代码的解耦性。 #### 23. Introduce Design By Contract checks 通过在代码中引入契约式设计(Design By ...

Global site tag (gtag.js) - Google Analytics