This list is the result of a question posed by Mika Moilanen. It's not comprehensive, but I think it's illustrative of a way to break down these choices that's a tad more healthy than "always choose X" or "never do Y." Specifically because you can take these properties and evaluate them against a given set of conditions.
"List all the differences between using an abstract class and using a delegate object when sharing common functionality."
class Adder {
int add(int x, int y) {
return x + y;
}
}
class Composition {
private Adder a = new Adder();
int add(int x, int y) {
return a.add(x, y);
}
}
abstract class Adder {
int add(int x, int y) {
return x + y;
}
}
class Inheritance extends Adder {
// Implicitly inherits add
}
class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
int mul(int x, int y) {
return x * y;
}
int div(int x, int y) {
return x / y;
}
}
class Composition {
private MathDoer m = new MathDoer();
int add(int x, int y) {
return m.add(x, y);
}
int sub(int x, int y) {
return m.sub(x, y);
}
int mul(int x, int y) {
return m.mul(x, y);
}
int div(int x, int y) {
return m.div(x, y);
}
}
abstract class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
int mul(int x, int y) {
return x * y;
}
int div(int x, int y) {
return x / y;
}
}
class Inheritance extends Adder {
// Implicitly inherits add, sub, mul, and div
}
abstract class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
}
class Inheritance extends Adder {
// Will print for both add and sub
// but brittle if sub is ever redefined
// to be "return x - y;"
int add(int x, int y) {
.println("Add called. x=" + x + ", y=" + y);
IOreturn super.add(x, y);
}
}
class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
}
class Composition {
private MathDoer m = new MathDoer();
// Resilient to whatever internal relationships
// add and sub have within MathDoer
int add(int x, int y) {
.println("Add called. x=" + x + ", y=" + y);
IOreturn m.add(x, y);
}
int sub(int x, int y) {
.println("Sub called. x=" + x + ", y=" + y);
IOreturn m.sub(x, y);
}
}
abstract class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return x - y;
}
}
class Inheritance extends MathDoer {
int add(int x, int y) {
.println("Add called. x=" + x + ", y=" + y);
IOreturn super.add(x, y);
}
}
void main() {
= new Inheritance();
MathDoer m .println(m.add(1, 2));
IO}
// To get dynamic dispatch you need an intermediate interface
interface IMathDoer {
int add(int x, int y);
int sub(int x, int y);
}
// This in turn means methods must be public.
//
// If you want "package-private" dynamic dispatch you
// are out of luck.
class MathDoer implements IMathDoer {
public int add(int x, int y) {
return x + y;
}
public int sub(int x, int y) {
return add(x, -1 * y);
}
}
class Composition implements IMathDoer {
private MathDoer m = new MathDoer();
public int add(int x, int y) {
return m.add(x, y);
}
public int sub(int x, int y) {
return m.sub(x, y);
}
}
void main() {
= new Composition();
IMathDoer m .println(m.add(1, 2));
IO}
abstract class Adder {
int add(int x, int y) {
return x + y;
}
}
abstract class Subber {
int sub(int x, int y) {
return x - y;
}
}
class Inheritance extends Adder { // Cannot also extend Subber
}
class Adder {
int add(int x, int y) {
return x + y;
}
}
class Subber {
int sub(int x, int y) {
return x - y;
}
}
class Composition {
private Adder a = new Adder();
private Subber s = new Subber();
// Resilient to whatever internal relationships
// add and sub have within MathDoer
int add(int x, int y) {
return a.add(x, y);
}
int sub(int x, int y) {
.sub(x, y);
s}
}
protected
members which are public only to classes in the same package and extenderspackage a;
abstract class MathDoer {
protected abstract int addInternal(int x, int y);
public final int add(int x, int y) {
return addInternal(x, y);
}
public final int sub(int x, int y) {
return addInternal(x, mul(y, -1));
}
}
package b;
// If extending the class, can see addInternal
// despite being in a different package
class Inheritance extends MathDoer {
protected int addInternal(int x, int y) {
return x + y;
}
}
package a;
// No way to define a method or field that will
// be visible across package boundaries only to things
// that are "composing".
final class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
}
abstract class MathDoer {
// Subclasses need to call a super-class constructor
MathDoer() {}
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
}
class Inheritance extends Adder {
Inhertiance() {
super();
}
}
class MathDoer {
private MathDoer() {}
public static MathDoer getIt() {
return new MathDoer();
}
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
}
class Composition {
// Meanwhile composition can allow you to keep constructors hidden
// and only expose static factories or builders.
private MathDoer m = MathDoer.getIt();
int add(int x, int y) {
return m.add(x, y);
}
int sub(int x, int y) {
return m.sub(x, y);
}
}
abstract class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
// int mul(int x, int y) {
// return x * y;
// }
}
class Inheritance extends Adder {
double mul(int x, int y) {
// This is fine initially, but poses a problem
// if an incompatible method is added to the superclass later
return ((double) x * y);
}
}
class MathDoer {
int add(int x, int y) {
return x + y;
}
int sub(int x, int y) {
return add(x, -1 * y);
}
int mul(int x, int y) {
return x * y;
}
}
class Composition {
private MathDoer m = new MathDoer();
// Resilient to whatever internal relationships
// add and sub have within MathDoer
int add(int x, int y) {
return m.add(x, y);
}
int sub(int x, int y) {
return m.sub(x, y);
}
// Any methods not taken via composition
// do not pose a problem.
//
// A caveat is that this is an issue
// with interfaces as well, so its more a property
// of polymorphic dispatch
double mul(int x, int y) {
return ((double) x * y);
}
}
Once I am able to cohere my thoughts without foaming at the mouth and twitching, expect a rant on "mechanics-ism."