HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

How could this object oriented design class be improved for future extensibility?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thisimproveddesigncouldextensibilityfutureforhoworientedobject

Problem

I wrote a C++ program for review and got feedback that I had not used any kind of extensibility of object orientation.

I am wondering if this is a clearly defined concept or one that is more in the eye of the beholder? How would you define using extensibility of object orientation?

My sample program took text and parsed it into words to produce an index of how many times each word was repeated in the text as well as the locations of each word as a sentence number. It then printed out the index. Abbreviations were to be included in the index, but other punctuation omitted (e.g. commas and colons). Upper and lower case versions of words were treated as the same word. I saw no need for virtual functions in solving this nor for class inheritance. For example, this paragraph would result in:

```
a 1:1
abbreviations 1:3
an 1:1
and 3:1,3,4
as 4:1,1,1,4
be 1:3
but 1:3
case 1:4
class 1:5
colons 1:3
commas 1:3
e.g. 1:3
each 2:1,1
example 1:6
for 3:5,5,6
functions 1:5
how 1:1
i 1:5
in 4:1,3,5,6
included 1:3
index 3:1,2,3
inheritance 1:5
into 1:1
it 2:1,2
locations 1:1
lower 1:4
many 1:1
my 1:1
need 1:5
no 1:5
nor 1:5
number 1:1
of 3:1,1,4
omitted 1:3
other 1:3
out 1:2
paragraph 1:6
parsed 1:1
printed 1:2
produce 1:1
program 1:1
punctuation 1:3
repeated 1:1
result 1:6
same 1:4
sample 1:1
saw 1:5
sentence 1:1
solving 1:5
text 2:1,1
the 5:1,1,2,3,4
then 1:2
this 2:5,6
times 1:1
to 2:1,3
took 1:1
treated 1:4
upper 1:4
versions 1:4
virtual 1:5
was 1:1
well

Solution

The code shown implements only operator+=(word) but calls only operator+=(token). Is one defined trivially in terms of the other?

Unless you could reasonably expect some requirement for indexer += token; to do something different from indexer += token.word(), OR if you expected to have to replicate all that extra finger typing in MANY calls, go with the latter more explicit code -- it's clearer. On the other hand, it may be more OOPish for Token to have its own operator>> overload so that Indexer is not exposed to how a Token gets read. This change would dumb down the Token constructor to a minimal default constructor (called once) and move the current constructor actions into the operator>> implementation.

As for class hierarchy and virtuals, the one place I could picture it being applied to the current code is as a substitute for punctuationKind. Each of your enum values could be a different class derived from PunctuationKind and its effects on the Token could be encoded in a virtual function called like so:

void
Indexer::Token::stripTrailingPunctuation()
{
    // get the correct singleton.
    const PunctuationKind& punctuation = trailingPunctuationKind();
    if (punctuation.setSentenceState(*this)) { // false for PunctuationNone or abbrev.?
        dropTrailingPunctuation();
        stripTrailingPunctuation(); // Recurse to find more trailing punctuation.
    }
}


Overkill? Possibly. Maybe not if you reasonably expect to have to build additional handling for other punctuation with new and different behavior.

BTW, does your code properly handle abbreviations that have extra trailing punctuation?

That leaves only OOP for the sake of future code. The question to ask is what new requirements could be reasonably expected and how could the existing code be set up so that it would still meet the requirements.

So, factor out a base class where there is a reasonable expectation that some specific members or methods of a class will be useful under some generalized set of requirements while the other specific members or methods of that class will not be useful.

Make a function virtual wherever you reasonably expect that expanded requirements will call for different instances of a class to implement it differently when driven by the same calling code and when the choice of behavior can and should be decided when the instance is constructed.

Other kinds of requirement changes are better anticipated by generic programming techniques (templates), or by other kinds of refactoring (into referenced components).

All of these things have their place in OOP.

If you don't have a sense for how the requirements are likely to change, so that you have a clear idea of why you are applying any of these techniques, then you are better off keeping things simple -- fewer lines written means fewer lines to rewrite.

Code Snippets

void
Indexer::Token::stripTrailingPunctuation()
{
    // get the correct singleton.
    const PunctuationKind& punctuation = trailingPunctuationKind();
    if (punctuation.setSentenceState(*this)) { // false for PunctuationNone or abbrev.?
        dropTrailingPunctuation();
        stripTrailingPunctuation(); // Recurse to find more trailing punctuation.
    }
}

Context

StackExchange Code Review Q#8591, answer score: 3

Revisions (0)

No revisions yet.