Some Rough Draft TDD Demonstration Videos 203
I’m doing a series of videos on TDD. The ultimate result will be a much more polished version with embedded slides, and such. But as a part of the development process, I’m creating scratch videos.
Much of what you see in these videos will be in the final versions, but those are far in the future relative to this work.
Hope you find them interesting.
Comments welcome.
Here is what is already available:- Getting started
- Adding Operators
- Removing violation of Open/Closed principle
- Removing duplication in operations with a combination of the Strategy pattern and the Template Method pattern
- Adding new operators after the removal of duplication.
- Reducing coupling by using the Abstract Factory pattern, Dependency Inversion and Dependency Injection
- Adding a few more operations
- Allowing the creation of complex “programs” or “macros” by using the Composite pattern – and avoiding Liskov Substitution Principle inherent in the GoF version of the pattern
- Driving the calculator via FitNesse + Slim
Anyway, that’s the plan. I’ll try to add each of these videos over the next few weeks.
The Liskov Substitution Principle for "Duck-Typed" Languages 109
OCP and LSP together tell us how to organize similar vs. variant behaviors. I blogged the other day about OCP in the context of languages with open classes (i.e., dynamically-typed languages). Let’s look at the Liskov Substitution Principle (LSP).
The Liskov Substitution Principle was coined by Barbara Liskov in Data Abstraction and Hierarchy (1987).
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T.
I’ve always liked the elegant simplicity, yet power, of LSP. In less formal terms, it says that if a client (program) expects objects of one type to behave in a certain way, then it’s only okay to substitute objects of another type if the same expectations are satisfied.
This is our best definition of inheritance. The well-known is-a relationship between types is not precise enough. Rather, the relationship has to be behaves-as-a, which unfortunately is more of a mouthful. Note that is-a focuses on the structural relationship, while behaves-as-a focuses on the behavioral relationship. A very useful, pre-TDD design technique called Design by Contract emerges out of LSP, but that’s another topic.
Note that there is a slight assumption that I made in the previous paragraph. I said that LSP defines inheritance. Why inheritance specifically and not substitutability, in general? Well, inheritance has been the main vehicle for substitutability for most OO languages, especially the statically-typed ones.
For example, a Java application might use a simple tracing abstraction like this.
public interface Tracing {
void trace(String message);
}
Clients might use this to trace methods calls to a log. Only classes that implement the Tracer
interface can be given to these clients. For example,
public class TracerClient {
private Tracer tracer;
public TracerClient(Tracer tracer) {
this.tracer = tracer;
}
public void doWork() {
tracer.trace("in doWork():");
// ...
}
}
However, Duck Typing is another form of substitutability that is commonly seen in dynamically-typed languages, like Ruby and Python.
If it walks like a duck and quacks like a duck, it must be a duck.
Informally, duck typing says that a client can use any object you give it as long as the object implements the methods the client wants to invoke on it. Put another way, the object must respond to the messages the client wants to send to it.
The object appears to be a “duck” as far as the client is concerned.
In or example, clients only care about the trace(message)
method being supported. So, we might do the following in Ruby.
class TracerClient
def initialize tracer
@tracer = tracer
end
def do_work
@tracer.trace "in do_work:"
# ...
end
end
class MyTracer
def trace message
p message
end
end
client = TracerClient.new(MyTracer.new)
No “interface” is necessary. I just need to pass an object to TracerClient.initialize
that responds to the trace
message. Here, I defined a class for the purpose. You could also add the trace
method to another type or object.
So, LSP is still essential, in the generic sense of valid substitutability, but it doesn’t have to be inheritance based.
Is Duck Typing good or bad? It largely comes down to your view about dynamically-typed vs. statically-typed languages. I don’t want to get into that debate here! However, I’ll make a few remarks.
On the negative side, without a Tracer
abstraction, you have to rely on appropriate naming of objects to convey what they do (but you should be doing that anyway). Also, it’s harder to find all the “tracing-behaving” objects in the system.
On the other hand, the client really doesn’t care about a “Tracer” type, only a single method. So, we’ve decoupled “client” and “server” just a bit more. This decoupling is more evident when using closures to express behavior, e.g., for Enumerable
methods. In our case, we could write the following.
class TracerClient2
def initialize &tracer
@tracer = tracer
end
def do_work
@tracer.call "in do_work:"
# ...
end
end
client = TracerClient2.new {|message| p "block tracer: #{message}"}
For comparison, consider how we might approach substitutability in Scala. As a statically-typed language, Scala doesn’t support duck typing per se, but it does support a very similar mechanism called structural types.
Essentially, structural types let us declare that a method parameter must support one or more methods, without having to say it supports a full interface. Loosely speaking, it’s like using an anonymous interface.
In our Java example, when we declare a tracer object in our client, we would be able to declare that is supports trace
, without having to specify that it implements a full interface.
To be explicit, recall our Java constructor for TestClient
.
public class TracerClient {
public TracerClient(Tracer tracer) { ... }
// ...
}
}
In Scala, a complete example would be the following.
class ScalaTracerClient(val tracer: { def trace(message:String) }) {
def doWork() = { tracer.trace("doWork") }
}
class ScalaTracer() {
def trace(message: String) = { println("Scala: "+message) }
}
object TestScalaTracerClient {
def main() {
val client = new ScalaTracerClient(new ScalaTracer())
client.doWork();
}
}
TestScalaTracerClient.main()
Recall from my previous blogs on Scala, the argument list to the class name is the constructor arguments. The constructor takes a tracer
argument whose “type” (after the ’:’) is { def trace(message:String) }
. That is, all we require of tracer
is that it support the trace
method.
So, we get duck type-like behavior, but statically type checked. We’ll get a compile error, rather than a run-time error, if someone passes an object to the client that doesn’t respond to tracer
.
To conclude, LSP can be reworded very slightly.
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is substitutable for T.
I replaced a subtype of with substitutable for.
An important point is that the idea of a “contract” between the types and their clients is still important, even in a language with duck-typing or structural typing. However, languages with these features give us more ways to extend our system, while still supporting LSP.
Liskov Substitution Principle and the Ruby Core Libraries 79
There is a spirited discussion happening now on the ruby-talk list called Oppinions on RCR for dup on immutable classes (sic).
In the core Ruby classes, the Kernel
module, which is the root of everything, even Object
, defines a method called dup
, for duplicating objects. (There is also a clone
method with slightly different behavior that I won’t discuss here.)
The problem is that some derived core classes throw an exception when dup
is called.
NilClass, FalseClass, TrueClass, Fixnum,
and Symbol
) that do this. Consider, for example, the following irb session:
irb 1:0> 5.respond_to? :dup => true irb 2:0> 5.dup TypeError: can't dup Fixnum from (irb):1:in `dup' from (irb):1 irb 3:0>If you don’t know Ruby, the first line asks the
Fixnum
object 5
if it responds to the method dup
(with the name expressed as a symbol, hence the ”:”). The answer is true, becuase this method is defined by the module Kernel
, which is included by the top-level class Object
, an ancestor of Fixnum
.
However, when you actually call dup
on 5
, it raises TypeError
, as shown.
So, this looks like a classic Liskov Substitution Principle violation. The term for this code smell is Refused Bequest (e.g., see here) and it’s typically fixed with the refactoring Replace Inheritance with Delegation.
The email thread is about a proposal to change the library in one of several possible ways. One possibility is to remove dup
from the immutable classes. This would eliminate the unexpected behavior in the example above, since 5.respond_to?(:dup)
would return false, but it would still be an LSP violation, specifically it would still have the Refused Bequest smell.
One scenario where the current behavior causes problems is doing a deep copy of an arbitrary object graph. For immutable objects, you would normally just want dup
to return the same object. It’s immutable, right? Well, not exactly, since you can re-open classes and even objects to add and remove methods in Ruby (there are some limitations for the immutables…). So, if you thought you actually duplicated something and started messing with its methods, you would be surprised to find the original was “also” modified.
So, how serious is this LSP issue (one of several)? When I pointed out the problem in the discussion, one respondent, Robert Dober, said the following (edited slightly):
I would say that LSP does not apply here simply because in Ruby we do not have that kind of contract. In order to apply LSP we need to say at a point we have an object of class Base
, for example. (let the gods forgive me that I use Java)
void aMethod(final Base b){ .... }and we expect this to work whenever we call aMethod with an object that is a Base. Anyway the compiler would not really allow otherwise.
SubClass sc; // subclassing Base od course aMethod( sc ); // this is expected to work (from the type POV).
Such things just do not exist in Ruby, I believe that Ruby has explained something to me:
- OO Languages are Class oriented languages
- Dynamic Languages are Object oriented languages.
Replace Class with Type and you see what I mean.
This is all very much IMHO of course but I feel that the Ruby community has made me evolve a lot away from “Class oriented”.
He’s wrong that the compiler protects you in Java; you can still throw exceptions, etc. The JDK Collection classes have Refused Bequests. Besides that, however, he makes some interesting points.
As a long-time Java programmer, I’m instinctively uncomfortable with LSP violations. Yet, the Ruby API is very nice to work with, so maybe a little LSP violation isn’t so bad?
As Robert says, we approach our designs differently in dynamic vs. static languages. In Ruby, you almost never use the is_a?
and kind_of?
methods to check for type. Instead, you follow the duck typing philosophy (“If it acts like a duck, it must be a duck”); you rely on respond_to?
to decide if an object does what you want.
In the case of dup
for the immutable classes, I would prefer that they not implement the method, rather than throw an exception. However, that would still violate LSP.
So, can we still satisfy LSP and also have rich base classes and modules?
There are many examples of traits that one object might or should support, but not another. (Those of you Java programmers might ask yourself why all objects support toString
, for example. Why not also toXML
...?)
Coming from an AOP background, I would rather see an architecture where dup
is added only to those classes and modules that can support it. It shouldn’t be part of the standard “signature” of Kernel
, but it should be present when code actually needs it.
Kernel
, Module
, and Object
should be refactored into smaller pieces and programmers should declaratively mixin the traits they need. Imagine something like the following:
irb 1:0> my_obj.respond_to? :dup => false irb 2:0> include 'DupableTrait' irb 2:0> my_obj.respond_to? :dup => true irb 4:0> def dup_if_possible items irb 5:1> items.map {|item| item.respond_to?(:dup) ? item.dup : item} irb 6:1> end ...In other words,
Kernel
no longer “exposes the dup
abstraction”, by default, but the DupableTrait
module “magically” adds dup
to all the classes that can support it. This way, we preserve LSP, streamline the core classes and modules (SRP and ISP anyone?), yet we have the flexibility we need, on demand.