Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Why study new and different programming languages? To change your programming mindset.
Not sure what I mean by that? Check this out.
Ever done one of these?
1: public interface IService
2: {
3: DateTime
GetDate();
4: int CalculateSomethingInteresting(int lhs, int rhs);
5: }
6:
7: public class OneServiceImpl
: IService
8: {
9: public DateTime
GetDate()
10: { return DateTime.Now;
}
11: public int CalculateSomethingInteresting(int lhs, int rhs)
12: { return lhs
+ rhs; }
13: }
14:
15: public class AnotherServiceImpl
: IService
16: {
17: public DateTime
GetDate()
18: { return new DateTime();
}
19: public int CalculateSomethingInteresting(int lhs, int rhs)
20: { return lhs
* rhs; }
21: }
22:
23: public class ServiceFactory
24: {
25: public static IService
GetInstance(string which)
26: {
27: if (which
== "One") return new OneServiceImpl();
28: else if (which
== "Another") return new AnotherServiceImpl();
29: else throw new ArgumentException();
30: }
31: }
32:
33: public class App
34: {
35: public static void Main(string[]
args)
36: {
37: foreach (string s in args)
38: {
39: IService
serv = ServiceFactory.GetInstance(s);
40: Console.WriteLine("serv
calc = {0}", serv.CalculateSomethingInteresting(3, 3));
41: }
42: }
43: }
So has my client this week. In fact, it's fair to say that they're infatuated with
them—they've got services all over the place, including at their communication layer,
where they use configuration files to decide which of the two service implementations
to use, either a "native" .NET implementation or the "real" Web
services implementation that they're supposed to be using. (They end up going back
to the native implementation because sometimes—which is to say, apparently a lot of
times—the Web services implementation is broken in some fundamental way. Go figure.)
The problem is, very bluntly, that the interfaces they're defining (the IService definition
above) are ever-so-slightly different from the communications-based proxy interfaces
that they use to communicate outside of this process, so some poor schmuck ends up
having to write the service implementation (OneServiceImpl) that simply takes the
parameters passed in, translates them into a call through the communications-based
interface, then takes the response and hands it back. Tedious, mind-numbing coding,
particularly painful when there are dozens of interfaces with (in some cases) hundreds
of methods per interface. Ouch.
There had to be a better way.
Based on some of the work/research/play I've been doing with both dynamic and functional
programming languages, it occurred to me that what they really wanted was some kind
of "forwarding" or "delegating" behavior that certain languages
have baked in as a feature. In those languages, it's possible to nominate a "delegate"
object to which method calls are automatically forwarded if no such method is implemented
on this object; in this particular case, what I'd do to replace all of the above is
simply create an IService object instance that has either a OneServiceImpl or a AnotherServiceImpl
instance (depending on the value in the configuration file) set up as the "delegate"
object. That way the method calls remain statically type-checked, but none of this
service interface/service implementation/service factory nonsense has to be created
just to switch between the two.
(By the way, all of this pain goes away completely in a language that supports deferred
checking of signatures until runtime. In other words, if the client had been programming
in IronPython or IronRuby or even Visual Basic, we could get away with not having
to do any of the above, and just use Reflection to access the appropriate method on
whichever of the two service implementations they want to use at the time. Fan would
let us do it if we used "->" instead of "." to invoke the method; Cobra would
switch between the two automatically; and so on.)
Now, this is C# 2.0 that they're using, and they're pretty entrenched on that point,
so I can't simply suggest that they use a new language, but if we take the basic idea
and adapt it to C#, we can get pretty much the same behavior without having to force
the poor schmuck on the bottom of the totem pole to write all those service implementations
by hand.
We start by transforming the IService interface into an IService "interface"
(meaning it's not really an interface anymore, but it'll sure look like one to anybody
who's not paying attention):
1: public class IService
2: {
3: public Func0<DateTime>
GetDate;
4: public Func2<int, int, int>
CalculateSomethingInteresting;
5: }
IService is now a class with fields (not properties, though I suppose if you really
wanted them to be properties you could make them such, not that I see much value to
doing so), where each field corresponds in name to the method of the interface it
wants to replace, and the type is a delegate type parameterized to match the return
type and parameter types of that same method of the original interface. Func0 and
Func2 are delegate types I had to create, since nothing like them existed until C#
3.0; their definitions are pretty simple:
1: public delegate R
Func0<R>();
2: public delegate R
Func1<R, P1>(P1 p1);
3: public delegate R
Func2<R, P1, P2>(P1 p1, P2 p2);
Now, assuming we have the implementation classes from before, we have two choices;
one is to write a by-hand factory that fills out the fields to point to the appropriate
method on the implementation class, like so:
1: if (which
== "One")
2: {
3: servInstance.GetDate
= delegate() { return DateTime.Now;
};
4: servInstance.CalculateSomethingInteresting
= delegate (int lhs, int rhs)
{ return lhs + rhs; };
5: }
6: else if (which
== "Another")
7: {
8: servInstance.GetDate
= delegate() { return new DateTime();
};
9: servInstance.CalculateSomethingInteresting
= delegate (int lhs, int rhs)
{ return lhs * rhs; };
10: }
11: else
12: throw new ArgumentException();
But, quite frankly, this defeats the point—the point was to avoid writing
all this stuff by hand, not simply repeat it in a different form. So instead, we leverage
Reflection, which depends on the basic assumption that the field name in the IService
"interface" matches the method name on the implementation class we wish
to invoke. Assuming that holds (which it does, in my client's case, anyway), we can
reflect on the IService field, find the matching method name in the implementation,
then construct a delegate instance around that method and assign the delegate instance
to the field. Once complete, we hand back the completed service instance, and the
client literally doesn't know that anything's different:
1: public class ServiceFactory
2: {
3: public static IService
GetInstance(string which)
4: {
5: IService
servInstance = new IService();
6:
7: Type
targetType = Assembly.GetExecutingAssembly().GetType(which + "ServiceImpl");
8:
9: foreach (FieldInfo
fi in servInstance.GetType().GetFields())
10: {
11: MethodInfo
targetMethod = targetType.GetMethod(fi.Name);
12: //Console.WriteLine("Wiring
up {0} against {1} with {2}", fi.Name, targetType, targetMethod);
13: Delegate
d = Delegate.CreateDelegate(fi.FieldType, null,
targetMethod);
14: //Console.WriteLine(d);
15: fi.SetValue(servInstance,
d);
16: }
17:
18: return servInstance;
19: }
20: }
Remember, the client code still looks the same...
1: public class App
2: {
3: public static void Main(string[]
args)
4: {
5: foreach (string s in args)
6: {
7: IService
serv = ServiceFactory.GetInstance(s);
8: Console.WriteLine("serv
calc = {0}", serv.CalculateSomethingInteresting(3, 3));
9: }
10: }
11: }
... because what the client doesn't know is that he's accessing a field, then invoking
the delegate that's being returned from that field dereference.
What this permits, aside from the automated wiring up of the IService "interface",
is a greater degree of flexibility—rather than having to choose which implementation
to use on an interface-by-interface basis, we can now configure to use different implementations
on a method-by-method basis. But considering how many interfaces and implementations
my client was looking at having to write by hand, the real win is in the automated
ServiceFactory wiring.
By the way, the only reason we can get away with this sleight-of-hand is because delegates
are deliberately designed to act like method calls; no explicit .Invoke() call is
required, it's implied with the () after the delegate instance's name. If Java7 closures
and/or method handles end up with support for that kind of syntax, then we can do
the same thing in Java7 (more or less).
Make sense?
Enterprise consulting, mentoring or instruction. Java, C++, .NET or XML services.
1-day or multi-day workshops available. Contact
me for details.