I saw this pattern somewhere:
class A extends B {
}
This structure is a little unusual to extend a generic by specifying the new
It is indeed perplexing, since the two types A
and B
appear to rely on each other to exist; that doesn't make much sense in ordinary OOP, so what is it for? I found 3 use cases for this pattern.
Say a Node
has a list of child Nodes. The usual design is through composition
class Node
ArrayList children = ...
Sometimes for a small performance gain, people use inheritance instead
class Node extends ArrayList
// the super class represents the children...
This is a little confusing, but there's nothing hard to understand. We know it's just a convenience, it does not try to convey that a node is a list of nodes.
LoadableComponent
can be considered of this use case. It's arguably a less ideal design than a composition approach
class ComponentLoader
C get(){...}
class EditIssue
final ComponentLoader loader = new ComponentLoader(){
@Override void load(){...}
@Override void isLoaded(){...}
};
EditIssue compo = ...
compo.loader.get().doSomething();
The designer might find this approach more boiler platey.
Instead of writing
foo.doA();
foo.doB();
a lot of people would rather want to write
foo.doA().doB();
Unfortunately the language doesn't directly support method chaining even though it is becoming an increasingly desired feature. The workaround is for doA()
to return foo
. It is a little dirty but acceptable.
However if foo
is in a type hierarchy the workaround is broken
class Bar
Bar doA()
class Foo extends Bar
Foo doB();
foo.doA().doB(); // doesn't compile, since doA() returns Bar
So some people call for a special "self type" to solve this problem. Let's say there's a keyword This
to represent "self type"
class Bar
This doA()
foo.doA().doB(); // works, doA() returns the type of foo, which is Foo
It appears that method chaining is the only use case for "self type", so the language probably will never introduce it (it's better to just support method chaining directly)
People found out that generics provides a workaround for this problem
class Bar
This doA()
class Foo extends Bar
Foo has a method "Foo doA()", inherited from Bar
This is the most popular use case for the A extends B
pattern. It is an isolated workaround/trick. It adds no semantics in relationship between A and B.
It is also a popular practice to constraint This
like
class Bar>
It is ugly and useless, I strongly recommend against it. Simply use "This" as a convention to indicate what it is for.
LoadableComponent
can also fall in this use case. In a simpler design we could do
class LoadableComponent
void ensureLoaded()
class EditIssue extends LoadableComponent
EditIssue compo = ...
compo.ensureLoaded();
compo.doSomething();
To support method chaining of the last two lines, LoadableComponent
is designed in its current form, so that we can write compo.get().doSomething()
So the previous two use cases are kind of hacks. What if there's a genuine constraint between A
and B
?
Rather than serving as an ordinary super type, B
is more meta, it describes that a type A
should have some properties that reference A
itself. This is not inheritance in traditional OOP's sense, it is something more abstract. (Though it is still implemented through traditional inheritance mechanism, it's imaginable that the language can promote it as a standalone concept.)
Comparable
is of this use case. It describes that a certain type can compare to itself. Since it is not a traditional OOP type, ideally we should never declare an object with static type Comparable
. We don't see it in public method return/parameter type, it won't make much sense. Instead we see things like
>
void sort(List)
here the method requires a type that conforms to the Comparable pattern.
(I don't really know what I'm talking about in this section)