: "Abstract" means, "to ignore irrelevant detail." So there must always be a goal, a test of relevance.
. You implement a concrete class pragmatically. You implement another ditto. You notice that the classes have a lot in common. You refactor to make one a subclass of another. You notice that the top one has just a few methods overridden in the bottom. You decide the system would look better if there were a class at the top with no concrete implementation of those methods, and two parallel subclasses with those methods. You do it.
The result is that you have an abstract class with two concrete subclasses. You have done it with no goal in mind other than to reduce redundancy in a particular way.
I've done this exercise and wound up with a very nice implementation of the classes in question and never had a goal in mind of the kind of abstraction I was doing. It's almost mechanical. Very interesting.
And each step of the way you had a working implementation with no stubs yet to be implemented, no worry about whether you were going to be able to implement (and like) a pre-conceived design. By building the classes one step at a time, you arrived at a better end-point than you could have pre-conceived. But it did take the willingness to incrementally modify existing design and
implementation. Which is in accord with the philosophy of ChristopherAlexander
I am currently teaching programmers that there are only two criteria a program must meet to be a well-built program:
- It must satisfy the consumer by solving the (current) problem and all of the subproblems it entails. (See SoftwareAsEnterprise for a mini-rant on this issue that I'm already embarrassed I wrote.)
- It must solve every sub-problem OnceAndOnlyOnce.
Almost everyone grants the first criterion without demurrer, but controversy is usually stirred by the second. My rationale for making the bold statement is straightforward: all the other definitions of well-built software that I've used spawn more (and harder) questions than they answer. OAOO as an abstraction goal is no magic coin: there are still lots of judgement calls to be made. But the close calls seem fewer, easier to make, more open to reversal, and more coherently argued over. -- MichaelHill
"No goal in mind other than to reduce redundancy"
This isn't a surprising result. By the time you have the two classes, you already have your relevancy criteria; they are implicit in the class interfaces.
Abstraction is tied to polymorphism, which is about treating objects which are different at one level of abstraction as if they were the same at another level of abstraction. When you notice the classes "have a lot in common", you're identifying a level of abstraction at which they can be treated the same.
I don't see any conflict between the two approaches. Reducing redundancy is a suitable goal for abstraction. -- DaveHarris
(Some discussion originally on OnceAndOnlyOnce:)
is a good maxim, it doesn't address the situation where two notationally similar texts are actually at different levels of abstraction. Maybe this should be identified as a "force" driving the texts apart.
This depends on the support from the language! With adequate support,
you must be able to specialize in a separate scope, thus not dealing
with different levels of abstractions in the same place.
requires in addition is version control support:
the locality is defined at the level of the element, not at that of
is a maxim or principle written in the form of a pattern. As such it is subject to much opinion and disagreement. There are, however, a number of patterns that embody the principle ...
- One pattern is where I see an expression duplicated in several places, and turn it into a method/function/subroutine.
- Another pattern is where I see a set of similar functions and make a class/data structure out of it. Templates or generics are a closely related technique.
- Another way to code something only once is to make a little language and to have the "only once" be in the code generator. This is closely related to developing a metalevel architecture, but I think these two techniques are quite a bit different from simply making a method or a class to reduce duplication.
On the other hand, the way you use these techniques is similar. It is hard to tell whether two things are similar or not. I usually discover similarity after the fact, i.e. it is easier to tell that two methods are similar after I write them than before. So, I tend to look at code I write and say "I've seen that before!" and then try to figure out where. When I find similarities, rewrite to make sure that I write something OnceAndOnlyOnce
. This is the same whether I am writing a method or a metalevel architecture.