Tom Tees wrote:And, by the way, "registration" is a noun and so would be a great candidate to be a class/object.
Yes, I could have written my first sentence a bit better; it was afterwards I noticed that "register" could be a noun as well, and I risked going off on a tangent about the orthogonality of natural languages and parts of speech....
So you take a "proper" use-case, and you find objects that can act out that use-case, i.e. you start to functionally decompose a use-case into one of more objects?
Effectively, yes; something I don't do enough of, but which fits in well with the idea is that of Behaviour-driven development: fleshed out with example hard data they can be formalised in code and serve as programs that test the system under construction.
Can you provide some real-world examples of this?
You lost me there. I'm still weak with more advanced concepts like interfaces.
As far as "real-world" goes, the more popular and robust frameworks such as Zend's - the sort that have been out in the real world enough to have been well-refined.
For my own example is a class library I'm just starting, for manipulating graphs. Algorithmic Graph Theory is a whole branch of mathematics, and there is a huge array of different attributes that a graph may or may not have: a graph might be connected, a tree, bipartite - or any combination. There is no consistent class hierarchy that can be imposed, so there's no help in making, say, BipartiteGraph and TreeGraph as subclasses of Graph, because what if I want a bipartite tree? One of them would have to subclass the other, and then I would either have "all bipartite graphs are trees" or "all trees are bipartite" - which is bogus.
So instead they're declared as interfaces. A class is free to implement IBipartiteGraph regardless of its pedigree. Then all that's required of the class is that it implement (somehow) all the methods the interface lists (e.g., the defining characteristic of a bipartite graph is that its vertices can be grouped into two sets, with all of the neighbours of any vertex in either set being in the other set, so there would be a method that returns the two sets of vertices).
Interfaces can have their own hierarchy. ITreeGraph and IBipartiteGraph both extend IGraph, and there would also be an IRootedTreeGraph (I've got to work on those names, I think; namespaces?), which does everything a tree (and therefore graph) does, plus distinguishes one vertex as special.
Now the other part:
Weedpacket wrote:If there are a bunch of methods that have a lot to do with each other but little to do with the rest of the class then perhaps they should take on a life (and instance) of their own
One example is from your use case: there are a whole slew of steps involved in registration, and a whole slew of methods to go with it. They're going to be passing all sorts of stuff about and calling each other, but they're not going to be having much to do with, say, the methods for tweaking a user's profile preferences. That suggests that registering and tweaking belong in separate classes.
From my graph library, a lot of algorithms involve searching for paths with various properties. A lot of the time the search itself is the same, with only the property being checked differing.
I could lump all of the algorithms into whatever class they're appropriate to, but there are major problems.
Most of the time I don't have classes, I have interfaces. I might provide basic implementations so that library's users don't have to build the whole library again from scratch, but I can't put the algorithms there or the users won't be able to use them for their own implementations.
Every time I add a new method, I end up changing its interface, and then every other implementation of that interface has to be updated - and not just mine, but everyone's.
A given algorithm often isn't just one method: there's initialisation and clean-up, adjusting parameters, intermediate results, and on and on. Each algorithm therefore introduces a cluster of methods and properties that relate tightly to each other, but only generally with the class as a whole, and hardly ever with other algorithms.
The solution is not to think of the Graph as something that does what the algorithm specifies, but instead think of the algorithm as a machine that works on graphs. Make the algorithm a class, instantiate it while passing the graph in via its constructor, adjust its settings, and start it running.
As long as the supplied graph implements the interface(s) the algorithm expects it to, all should be happy.