Lately I've used this technique a few times, and I'm trying to figure out if it has a name, if it's one of the patterns, etc.
In complicated situations where I might normally have a number of giant select case statements, I've been experimenting with a more plugin-style approach.
I create an interface, let's called it IProblemHandler, and maybe that interface exposes a property enumerating the kinds of problems it can handle (IProblemHandler.HandledProblems), or even better, maybe the problem it handles is embedded in the class name - convention over configuration.
Then, instead of a select case statement, I call some code that reflects through all the classes in a certain assembly and pulls the ones that implement the interface, then it determines which one is appropriate for the problem at hand, instantiates it, and solves the problem.
In the past I've used it as a plugin architecture, but it's turning out to be really useful as a way to handle certain kinds of complexity. It seems like maybe this is the domain of IoC containers and so on. I know the managed extensibility framework does some of this stuff.
So... Do you know of a name for this sort of thing?
-
I think that your approach is a bit heavy. Some powerful languages can make life easier: clojure.org/multimethodsJob– Job2011年07月02日 03:42:40 +00:00Commented Jul 2, 2011 at 3:42
-
1Would you mind explaining why you feel like this approach is better (subjectively or otherwise) than switches? If you HAVE to do that to solve your problems it sounds like you're not using a language that's working for your benefit.Jordan– Jordan2011年07月02日 08:29:59 +00:00Commented Jul 2, 2011 at 8:29
-
What are the criteria for choosing a handler? Is there any situation where two handlers are equally qualified for handling a particular request? (Those little details matters in the description of a pattern.)rwong– rwong2011年07月02日 09:35:25 +00:00Commented Jul 2, 2011 at 9:35
-
Jordan: Sure. (1) It eliminates maintenance of the switches; there could be one handler that does ten things, so that could actually be ten switches if you're accessing everything through a single overarching class. (2) That means that adding or removing a handler is literally as simple as dropping a new class in the project (or deleting one). (3) It provides very clean encapsulation for the handler code and everything is loosely coupled. And yeah, there are tons of other ways you could get these benefits. This is just something I've been playing with lately and so far I like it. :)Brian MacKay– Brian MacKay2011年07月02日 17:34:27 +00:00Commented Jul 2, 2011 at 17:34
-
rwong: Hm, right now I've only done them where there's a one-to-one relationship. I could imagine scenarios where several options could be attempted, the results could be measured, and then the optimal solution could be used. But now I think we may be geting farther away from patterns. :)Brian MacKay– Brian MacKay2011年07月02日 17:36:32 +00:00Commented Jul 2, 2011 at 17:36
6 Answers 6
I don't think this is a design pattern as such (at least not by itself). To me it sounds like you're using reflection to simplify registration when using an IoC container.
For example, if you have multiple problem solvers for your problems and you have something like this:
public interface ISolveProblems<T> where T: Problem
{
void Solve(T problem);
}
public abstract class Problem
{
}
public class BigProblem : Problem
{
}
public class BigProblemSolver : ISolveProblems<BigProblem>
{
public void Solve(BigProblem problem)
{
// do whatever
}
}
public class SomeOtherBigProblemSolver : ISolveProblems<BigProblem>
{
public void Solve(BigProblem problem)
{
// do whatever
}
}
Then registering everything by hand is a pain once you have hundreds of the things, but using reflection you can wire it all up really easily on application startup. This isn't chain of responsibility, although you can set that up with this technique as well.
I'm not sure if this has a specific name, but in general I think having things set up in a way that allows this means you have a 'pluggable architecture'.
-
This is the best answer I found -- it's not a pattern, it's a pluggable architecture. Although, if it has a name and it's easily reproducable in a code snippet, it kind of is a pattern after all. Maybe I will start calling it the Pluggable Solver Pattern.Brian MacKay– Brian MacKay2011年08月09日 20:39:02 +00:00Commented Aug 9, 2011 at 20:39
-
Or maybe Pluggable Handler? Pluggable Processor? I think I like Pluggable Processor Pattern.Brian MacKay– Brian MacKay2011年08月09日 20:41:27 +00:00Commented Aug 9, 2011 at 20:41
It's essentially a slightly modified version of the Chain of Responsibility pattern, straight out of the GoF.
In the COR, a command object is routed to a dispatcher, which references several handlers, each of which declare (usually via a method, but via attributes or convention is also fine) whether or not they're able to handle the command. If so, then they handle it; if not, it's passed on to the next handler.
-
Agreed, with modifications. Perhaps though, refactoring your code to look more like the pattern may add some clarity to your existing code?Mike H– Mike H2011年07月11日 03:51:40 +00:00Commented Jul 11, 2011 at 3:51
-
Thanks for the thoughtful answer. CoR does solve a similiar problem, but I think this is different enough that it's not CoR anymore. Let me explain.. In my understanding, CoR is for when you have a bunch of solvers and you want them to take a stab at the problem in a specific order by following a line of succession. This line of succession requires configuration. What I'm talking about is more like: there are 10 kinds of images, and each has a class that handles it. If we want to also handle TIFFs some day, that's just a matter of dropping a new class in that implements a certain interface.Brian MacKay– Brian MacKay2011年08月09日 20:49:51 +00:00Commented Aug 9, 2011 at 20:49
-
@Brian: I maintain that it's a CoR, regardless of how many "problems" each solver "solves", because (at least as far as I can tell) in your design you still must iterate through all of the handlers until you find the one that works. If you had a central registry, that would be something different - specifically it would be a service locator or possibly a vanilla factory method.Aaronaught– Aaronaught2011年08月09日 22:39:24 +00:00Commented Aug 9, 2011 at 22:39
-
@Aaronaught: Curious as to why a central registry makes the difference to you? In CoR, I feel there's more of a central repository than in the one I'm describing, since in CoR you have to wire up the chain somewhere, whereas with this one you don't. Also: To me it's not about how many problems each solver solves -- I always thought CoR was for establishing order you want to attempt various solutions in, or even more powerfully, for building out a pipeline. I'm talking about a pluggable model for dropping in solutions to new problems. Please don't take this as critical - I'm trying to learn. :)Brian MacKay– Brian MacKay2011年08月10日 14:41:10 +00:00Commented Aug 10, 2011 at 14:41
I'm not sure it's the Chain of Responsibility pattern in this case. You're substituting explicit case statements with a reflection (type of a meta-programming) based on naming conventions. It seems to me you're somewhere in between DSL and IoC.
I have an alternative pattern - have a dictionary mapping values to function pointers / lambdas etc.
Python version (C# and C and C++ and many others can do this too)
>>> def p1(): print("hi")
...
>>> def p2(): print("hello")
...
>>> d = {'p1' : p1, 'p2' : p2}
>>> d['p1']()
hi
>>>
-
This approach is usually called a "dispatch table" in pythonDaenyth– Daenyth2011年07月02日 17:58:32 +00:00Commented Jul 2, 2011 at 17:58
This is called a Service Locator, and is used in many places including OSGI. Except that locators do not usually scan classes, but use some configuration files. Numerous plugin-based apps are implemented atop of OSGI, including Eclipse and Android.
A similar pattern can be found in another plugin-based app - Netbeans, the mechanism called "lookup". You specify an interface and receive its implementation.
Sounds like a combination of the Strategy Pattern and the Factory Method Patterns to me.
Factory / Factory Method Pattern
The factory method pattern is an object-oriented design pattern to implement the concept of factories. Like other creational patterns, it deals with the problem of creating objects (products) without specifying the exact class of object that will be created.Strategy Pattern
In computer programming, the strategy pattern (also known as the policy pattern) is a particular software design pattern, whereby algorithms can be selected at runtime. Formally speaking, the strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
The two patterns are often used in conjunction with each other as the Factory Method pattern deals with the mechanism by which the correct objects are instantiated (it's a creational pattern), and the Strategy Pattern deals with how the differing algorithms implemented by the differing objects created by the Factory Pattern are used and selected (since it's a behavioural pattern).
Certainly, it's most often the Strategy Pattern that is used as a mechanism to replace excessive switch or case selections.