How to solve parallel Inheritance in UI case

2020-04-17 04:21发布

问题:

I have a problem to understand how to solve parallel inheritance.

Definition of Parallel Inheritance

Fowler definies parallel inheritance as follows [1, page 68]:

Parallel inheritance hierarchies is really a special case of shotgun surgery. In this case, every time you make a subclass of one class, you also have to make a subclass of another. You can recognize this smell because the prefixes of the class names in one hierarchy are the same as the prefixes in another hierarchy.

Problem

In the Book Refactoring in Large Software Projects: Performing Complex Restructurings, page 46 the author displays the following parallel inheritance:

Then he solves the parallel inheritance with composition and says:

In many cases, parallel inheritance hierarchies can be resolved in such a manner that only one inheritance hierarchy is left, while the classes of other inheritance hierarchies are integrated through use.

The solution looks like this:

So the author and Fowler concludes that a parallel inheritance can be solved with

The general strategy for eliminating the duplication is to make sure that instances of one hierarchy refer to instances of the other.

The problem which I see is the classes are still there and if I add a new class a new '*ListView' class must be added.

But for this problem Fowler says:

If you use Move Method and Move Field, the hierarchy on the referring class disappears.

For the current case this means I move the method to display the entities in the entity classes?

So this will hurts the MVC principal or not?!

QUESTION

So how to solve parallel inheritance at all and especially in UI cases?

SOURCE:

1 Martin Fowler 'Refactoring: Improving the Design of Existing Code'

[Book: Refactoring in Large Software Projects: Performing Complex Restructurings, page 46]

回答1:

There are several ways of solving this, depending on the case. Here I present two possible solutions:

Note: I will use some scala-like pseudo code for the examples just because is short and readable

List Adapter & Generics

Based on implementations from:

  • Android ListView
  • Javax JList
  • Arena MVVM Framework

Initially, ListView could have a default adapter which requires its adaptees to implement some interface, let's call it Nameable. And uses it to render each item.

  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
  }

Of course, not all the classes will be rendered exactly the same way. This is solved by the use of an Adapter.

  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*/ 
  }

Where a ListView of suppliers will generate a list of company names and a ListView of customers will display a list of client codes with their respective names (whatever, just an example).

Now, if we want to make more complex lists, we could feed the ListView with a custom adapter.

  class PhoneNumberAdapter extends ListAdapter[Supplier] { /*...*/}

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

Using this technique, you no longer need one ListView class per model class, and only define custom adapters for special cases. The better your default adapter and your hierarchy, the less code you will need.

For another example, you can take a look at https://codereview.stackexchange.com/questions/55728/a-generic-mvc-arrayadapter-class


Mixins / Traits

Instead of using composition, if your language of choice allows it (and if you philosophically agree), you might use mixins or traits. I'll not dive in the differences between the two, and just use mixins (for more information: Mixins vs. Traits).

By using mixins you are able to decompose behaviour on a more refined way, unlocking a variety of new patterns and solutions (even more if you add some structural typing and other features).

For instance, back to the ListView case, each rendering strategy could be incorporated by the ListView, looking like:

  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

Where NameAndPhoneNumber could be applied to several classes, not only Customer or Supplier. In fact, it might be refactored to:

  new ListView[Supplier] with NameRendering with PhoneRendering

And make use of the Stackable Traits pattern (more info (here)[http://dl.acm.org/citation.cfm?id=2530439] and (here)[http://www.artima.com/scalazine/articles/stackable_trait_pattern.html])

References:

  • Traits paper
  • Mixins paper
  • Sibiling pattern
  • A case: Backbonejs
  • (Stackable Traits Pattern)[http://www.artima.com/scalazine/articles/stackable_trait_pattern.html]