public interface Salaried {
int salary();
}
public class Employee implements Salaried {
//..
//.. other methods
@Override
public int salary() {
// implementation
}
}
public class WageWorker implements Salaried {
//..
//.. other methods
@Override
public int salary() {
// implementation
}
}
public class Contractor implements Salaried {
//..
//.. other methods
@Override
public int salary() {
// implementation
}
}
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 Polymorphism
Structural 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 Adapter
Actually it gets better than this with Scala. Suppose in the above model, the name
salary is not meaningful for DailyWorkers 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.