I was going through a not-so-recent Java code base that contained the following structure for modeling the employee hierarchy of an organization. This looks quite representative of idiomatic Java being used to model a polymorphic hierarchy for designing a payroll generation application.
public interface Salaried {
int salary();
}
public class Employee implements Salaried {
@Override
public int salary() {
}
}
public class WageWorker implements Salaried {
@Override
public int salary() {
}
}
public class Contractor implements Salaried {
@Override
public int salary() {
}
}
And the payroll generation class (simplified for brevity ..) that actually needs the subtype polymorphism between the various concrete implementations of the
Salaried
interface.
public class Payroll {
public int makeSalarySheet(List<Salaried> emps) {
int total = 0;
for(Salaried s : emps) {
total += s.salary();
}
return total;
}
}
While implementing in Java, have you ever wondered whether using public inheritance is the best approach to model such a scenario ? After all, in the above class hierarchy, the classes
Employee
,
WageWorker
and
Contractor
does not have *anything* in common except the fact that all of them are salaried persons and that subtype polymorphism has to be modeled *only* for the purpose of generating paysheets for all of them through a single API. In other words, we are coupling the
entire class hierarchy through a compile time static relationship only for the purpose of unifying a
single commonality in behavior.
Public inheritance has frequently been under fire, mainly because of the coupling that it induces between the base and the derived classes. Experts say
Inheritance breaks Encapsulation and also regards it as the second strongest relationship between classes (only next to friend classes of C++). Interface driven programming has its advantages in promoting loose coupling between the contracts that it exposes and their concrete implementations. But interfaces in Java also pose problems when it comes to evolution of an API - once an interface is
published, it is not possible to make any changes without breaking client code. No wonder we find design patterns like
The Extension Object or strict guidelines for evolution of abstractions being enforced in big projects like Eclipse.
Finer Grained PolymorphismStructural typing offers the ability to reduce the scope of polymorphism
only over the subset of behaviors that need to be common between the classes. Just as in duck typing, commonality in abstractions does not mean that they belong to one common type; but only the fact that they respond to a common set of messages. Scala offers the benefit of both the worlds through its implementation of structural typing - a compile time checked duck typing. Hence we have a nice solution to unify certain behaviors of otherwise unrelated classes. The entire class hierarchy need not be related through static compile time subtyping relationship in order to be processed polymorphically over a certain set of behaviors. As an example, I tried modeling the above application using Scala's structural typing ..
case class Employee(id: Int) { def salary: Int =
case class DailyWorker(id: Int) { def salary: Int =
case class Contractor(id: Int) { def salary: Int =
class Payroll {
def makeSalarySheet(emps: List[{ def salary: Int }]) = {
(0 /: emps)(_ + _.salary)
}
}
val l = List[{ def salary: Int }](DailyWorker(1), Employee(2), Employee(1), Contractor(9))
val p = new Payroll
println(p.makeSalarySheet(l))
The commonality in behavior between the above classes is through the method
salary
and is only used in the method
makeSalarySheet
for generating the payroll. We can generalize this commonality into an anonymous type that implements a method having the same signature. All classes that implement a method
salary
returning an
Int
are said to be structurally conformant to this anonymous type
{ def salary: Int }
. And of course we can use this anonymous type as a generic parameter to a Scala
List
. In the above snippet we define
makeSalarySheet
accept such a
List
as parameter, which will include all types of workers defined above.
The Smart AdapterActually it gets better than this with Scala. Suppose in the above model, the name
salary
is not meaningful for
DailyWorker
s and the standard business terminology for their earnings is called
wage
. Hence let us assume that for the
DailyWorker
, the class is defined as ..
case class DailyWorker(id: Int) { def wage: Int =
Obviously the above scheme will not work now, and the unfortunate
DailyWorker
falls out of the closure of all types that qualify for payroll generation.
In Scala we can use implicit conversion - I call it the
Smart Adapter Pattern .. we define a conversion function that automatically converts
wage
into
salary
and instructs the compiler to adapt the
wage
method to the
salary
method ..
case class Salaried(salary: Int)
implicit def wageToSalary(in: {def wage: Int}) = Salaried(in.wage)
makeSalarySheet
api now changes accordingly to process a
List
of objects that either implement an
Int
returning
salary
method or can be implicitly converted to one with the same contract. This is indicated by
<%
and is known as a
view bound in Scala. Here is the implementation of the class
Payroll
that incorporates this modification ..
class Payroll {
def makeSalarySheet[T <% { def salary: Int }](emps: List[T]) = {
(0 /: emps)(_ + _.salary)
}
}
Of course the rest of the program remains the same since all conversions and implicit magic takes place with the compiler .. and we can still process all objects polymorphically even with a different method name for
DailyWorker
. Here is the complete source ..
case class Employee(id: Int) { def salary: Int =
case class DailyWorker(id: Int) { def salary: Int =
case class Contractor(id: Int) { def wage: Int =
case class Salaried(salary: Int)
implicit def wageToSalary(in: {def wage: Int}) = Salaried(in.wage)
class Payroll {
def makeSalarySheet[T <% { def salary: Int }](emps: List[T]) = {
(0 /: emps)(_ + _.salary)
}
}
val l = List[{ def salary: Int }](DailyWorker(1), Employee(2), Employee(1), Contractor(9))
val p = new Payroll
println(p.makeSalarySheet(l))
With structural typing, we can afford to be more conservative with public inheritance. Inheritance should be used *only* to model true subtype relationship between classes (aka
LSP). Inheritance definitely has lots of uses, we only need to use our judgement not to misuse it. It is a strong relationship and, as the experts say, always try to implement the least strong relationship that correctly models your problem domain.