如何解决UI案例中的并行继承问题

3
我有一个问题,不知道如何解决并行继承。

并行继承的定义

Fowler在[1, 第68页]中定义了并行继承:

并行继承层次结构实际上是散弹手术的一种特殊情况。在这种情况下,每当您创建一个类的子类时,您还必须创建另一个类的子类。您可以通过观察一个继承层次结构中的类名前缀与另一个继承层次结构中的类名前缀相同来识别此气味。

问题

在书中《大型软件项目重构:执行复杂重构,第46页》,作者展示了以下并行继承:

enter image description here

接着他用组合方式解决了并行继承问题,并说道:

在许多情况下,可以通过这种方式解决并行继承层次结构的问题,只留下一个继承层次结构,而其他继承层次结构的类则通过使用集成。

解决方案如下:

enter image description here

因此,作者和福勒得出结论:可以通过以下通用策略解决并行继承问题:

消除重复的一般策略是确保一个层次结构的实例引用另一个层次结构的实例。

我看到的问题是类仍然存在,如果我添加一个新类,则必须添加新的“*ListView”类。

但对于这个问题,福勒说:

如果使用Move Method和Move Field,则引用类上的层次结构将消失。

对于当前情况,这意味着我将显示实体的方法移动到实体类中?

那么这会违反MVC原则吗?!

问题

那么如何解决所有并行继承问题,特别是在UI案例中?

来源:

1 马丁·福勒《重构:改善既有代码的设计》

[书籍:大型软件项目重构:执行复杂重构,第46页]

1个回答

2
有几种解决方法,具体取决于情况。这里我提供两种可能的解决方案:
注意:我将使用一些类似于scala的伪代码来举例,只是因为它简短易读。
列表适配器和泛型
基于以下实现:
Android ListView Javax JList Arena MVVM Framework
最初,ListView 可能会有一个默认适配器,该适配器要求其适配器实现某个接口,让我们称其为 Nameable,并使用它来呈现每个项目。
  class BasicListViewAdapter extends ListAdapter[Nameable]

  //on scala it would be a trait, but I'll call it interface just for the sake of the example
  interface Nameable {
    def getName():String
  }

当然,并非所有的课程都会以完全相同的方式呈现。这可以通过使用适配器来解决。
  class Partner  implements Nameable
  class Customer extends Partner { 
    def getName = { this.getClientCode + " " + this.getFullName }
    /* some implementation*/ 
  }
  class Supplier extends Partner { 
    def getName = { this.getCompanyName }
    /* some implementation*/ 
  }

“ListView”控件可以生成供应商公司名称的列表,“ListView”控件也可以显示客户代码和相应名称的列表(这只是一个例子)。如果我们想要创建更复杂的列表,可以使用自定义适配器来填充“ListView”控件。
  class PhoneNumberAdapter extends ListAdapter[Supplier] { /*...*/}

  val supliersWithPhoneNumbers = new ListView
  supliersWithPhoneNumbers.useAdapter(new SupplierWithPhoneNumberAdapter)

使用这种技术,您不再需要针对每个模型类一个ListView类,并且仅为特殊情况定义自定义适配器。您的默认适配器和层次结构越好,您需要的代码就越少。
另一个例子,您可以看一下https://codereview.stackexchange.com/questions/55728/a-generic-mvc-arrayadapter-class

混入 / 特质

如果您所选的编程语言允许,并且您在哲学上同意,可以使用混入或特质来替代组合。我不会深入探讨两者之间的区别,只会使用混入(有关更多信息:混入 vs. 特质)。

通过使用混入,您可以更细致地分解行为,解锁各种新的模式和解决方案(如果添加一些结构化类型和其他功能,则更多)。例如,回到ListView案例,每个渲染策略都可以被ListView合并,看起来像:

  trait NameAndPhoneNumber {
    //we require this trait to be incorporated on some class of type ListView
    this:ListView[{def getName:String }] => 

    override def render = ...
  }


  //where ListView is covariant
  new ListView[Supplier] with NameAndPhoneNumber

“NameAndPhoneNumber” 不仅适用于顾客或供应商,还可以应用于其他几个类。实际上,它可能会被重构为:
  new ListView[Supplier] with NameRendering with PhoneRendering

并使用“可堆叠特质”模式(更多信息请参见(此处)[http://dl.acm.org/citation.cfm?id=2530439]和(此处)[http://www.artima.com/scalazine/articles/stackable_trait_pattern.html])。
参考资料:

1
感谢您的努力!我认为适配器可能是同样的问题,因为对于包含不同内容的每个listView都必须创建不同的适配器。而对于其他视图,必须为每个实体或内容创建一个视图。我已经意识到在UI情况下解决并行继承没有正确答案。但再次感谢 :) - Zelldon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接