Subtype dependency on corresponding subtypes

R

Robert Mark Bram

Hi All,

I have a design question.

I am writing an application that will search through the contents of
various files against a given search criteria. I envisage that there
will be different search strategies, each of which have different
search criteria. A couple of examples would be as per below.

Plain Text Strategy. Criteria: a single string (search term), case
sensitive flag.
Regular Expression Strategy: a single regular expression, number of
lines before and after each result to include, case sensitive flag.
XML Strategy: a single string (search term), name of the xml element
to be searched within.

I envisage having an abstract search type and concrete subclasses
based on each strategy. For example:

public interface Search {
SearchResult search(SearchCriteria criteria);
}

public class PlainTextSearchStrategy implements Search {
public SearchResult search(SearchCriteria criteria) {
// do the search..
}
}

public class RegularExpressionSearchStrategy implements Search
{ ... }

public class XmlSearchStrategy implements Search { ... }

My difficulty is the SearchCriteria. Each search strategy type would
have its own search criteria type and I do not think there is any
common behaviour (or data) that can be defined in the criteria super
type. As such, SearchCriteria is just a marker interface, and this
bothers me. The search strategy sub-types would need to cast
SearchCriteria to the specific type they need, so there is no type
safety beyond ensuring the marker interface is implemented. For
example:

public class PlainTextSearchStrategy implements Search {
public SearchResult search(SearchCriteria criteria) {
PlainTextSearchCriteria ptCriteria = (PlainTextSearchCriteria)
criteria;
// do the search..
}
}

Is there a design better suited to this problem of ensuring a sub-type
can rely on corresponding subtypes being present? As I understand it,
generics won't help me here either. Do I need to live with the
casting?

Any advice would be much appreciated!

Rob
:)
 
D

Dmitry A. Kazakov

I am writing an application that will search through the contents of
various files against a given search criteria. [...]
Is there a design better suited to this problem of ensuring a sub-type
can rely on corresponding subtypes being present? As I understand it,
generics won't help me here either. Do I need to live with the
casting?

Any advice would be much appreciated!

I am using the following design:

Abstract_Source maintains the source and the position in the source. The
interface supports source navigation. Implementations could be:
String_Soruce, File_Source, GUI_Entry etc.

Abstract_Scanner is an object that navigates the source. It could be a
full-blown parser or a simple pattern matcher, like Skip_Blanks,
Get_Literal, Find_String, Match etc.

Criteria are properties of a concrete scanner. It knows what it is doing.
If you want to parametrize a scanner, do not place the parameters in the
abstract interface of. Do it in a specific constructor / factory. For
example, a scanner that parses and executes a program in the source has
lots of parameters (it is table-driven), but these are the business of its
own.
 
E

Ed

Hi All,

I have a design question.
....
Any advice would be much appreciated!

Rob
:)



Three possibilities, in order of decreasing ugliness:

i) Have all the different criteria reside in the SearchCriteria, so
that each
different Search subtype can graze the appropriate parts of the
SearchCriteria
object.

For example, the SearchCriteria could be:

class SearchCriteria {
String getSearchTerm() {
...
}

String getRegularExpression() {
...
}

int getNumberOfLinesToInclude() {
...
}

etc.
}

Thus, the PlainTextStrategy will call getSearchTerm() but not
getRegularExpression() and the others, whereas
RegularExpressionStrategy will
call getRegularExpression() and getNumberOfLinesToInclude() but not
getSearchTerm().

ii) Separate the various SearchCriteria into separate classes such as
PlainTextCriteria, RegularExpressionCriteria, etc., and create a new
class,
CriteriaGroup, whose sole function is to store the various
SearchCriteria. For
example:

class CriteriaGroup {
void setPlainTextCriteria(PlainTextCriteria criteria) {
// Store object
}
PlainTextCriteria getPlainTextCriteria() {
return plainTextCriteria;
}

void setXmlCriteria(XmlCriteria criteria) {
// Store object
}
XmlCriteria getXmlCriteria() {
return xmlCriteria;
}

etc.
}

This will make the Search interface:

public interface Search {
SearchResult search(CriteriaGroup criteriaGroup);
}

Thus, again, the particular Search subtype will only retrieve the
appropriate
criteria; PlainTextStrategy will call
criteriaGroup.getPlainTextCriteria(),
etc.

iii) Item (ii) does rather beg the question: how is the SearchCriteria
object
being instantiated in your code today, and why are the instantiation
of the
SearchCriteria and Search subtype ojects being instantiated at
different
times/locations? If they were being instantiated together then why not
create
the Search subtype parameterised with the SearchCriteria it needs,
perhaps via
the constructor? (I think this is the solution Mister Kazakov is
suggesting.)

There's also a sneaking suspicion that the Search subtype hierarchy is
unjustified and it is the SearchCriteria itself that holds polymorphic
interest; perhaps the Search class can be eliminated and the
SearchCriteria
can be re-tasked to fill both roles?


..ed
 
H

H. S. Lahman

Responding to Bram...
I am writing an application that will search through the contents of
various files against a given search criteria. I envisage that there
will be different search strategies, each of which have different
search criteria. A couple of examples would be as per below.

Plain Text Strategy. Criteria: a single string (search term), case
sensitive flag.
Regular Expression Strategy: a single regular expression, number of
lines before and after each result to include, case sensitive flag.
XML Strategy: a single string (search term), name of the xml element
to be searched within.

I envisage having an abstract search type and concrete subclasses
based on each strategy. For example:

public interface Search {
SearchResult search(SearchCriteria criteria);
}

public class PlainTextSearchStrategy implements Search {
public SearchResult search(SearchCriteria criteria) {
// do the search..
}
}

public class RegularExpressionSearchStrategy implements Search
{ ... }

public class XmlSearchStrategy implements Search { ... }

My difficulty is the SearchCriteria. Each search strategy type would
have its own search criteria type and I do not think there is any
common behaviour (or data) that can be defined in the criteria super
type. As such, SearchCriteria is just a marker interface, and this
bothers me. The search strategy sub-types would need to cast
SearchCriteria to the specific type they need, so there is no type
safety beyond ensuring the marker interface is implemented. For
example:

My suggestion would be a variation on Ed's (ii) proposal. The strategy
subclass defines an algorithm for actually performing the search. That
algorithm will be generic to the overall type of search. The differences
between one specific search and another can be expressed in terms of
parametric data values.

If one can properly generalize the invariants of the search algorithm
and cast individual differences as data values, then all the client
needs to do is provide the data values that are appropriate to the
particular search (e.g., a string that is interpreted as a plain string
or a general expression, depending on the strategy).

However, a more robust solution will separate the concerns of When the
search needs to be done from the concerns of What algorithm to use and
the concerns of What specific data values must be supplied for the
search in hand. (There may also be a need to provide default values for
some parameters.)

The conventional way to do this is with a Strategy pattern and a
Specification object:

[Client]
| *
|
| R1
|
| triggers
| 1
[SearchAlgorithm]
A
| R2
+-------------------+---...
| |
[RegularExpression] [PlainText]
| * | *
| specifies | specifies
| |
| R3 | R4
| |
| 1 | 1
[RECriteria] [PTCriteria]
| |
+-------------------+----...
| R5
_
V
[SearchSpecification]

SearchAlgorithm and the R2 relationship represent the Strategy pattern.
The R1 relationship is instantiated by whoever understands which
algorithm needs to be used, which may not be the Client.

The R3/R4 relationships are instantiated by whoever understands what the
specific search criteria are. The SearchSpecification objects are just
dumb data holders for values that express the explicit search criteria
(strings, line counts, etc.). Those objects can be initialized with
defaults that are overridden by whoever understands the specific search
criteria. That may or may not be the Client.

The advantage is that it allows you to spread responsibilities around
various objects rather that hard-wiring them all into the Client object,
which could easily become a god-object if the searching and data
acquisition are complex and quite different.

[Note that in your specific example, no subclassing would be needed for
SearchSpecification because the attribute types would always be the
same. All that would change would the values (e.g., a string that is a
regular expression or plain text) and those would be properly
interpreted by the specific algorithm. Then the relevant relationship is
directly between SearchSpecification and SearchAlgorithm.]

Even better, it may be possible to initialize much, if not all, of the
SearchSpecification from external configuration data. Then you may be
able to add specific search criteria to the application without touching
the code. This is a very powerful technique. For <an extreme> example,
suppose the search algorithm is defined with an XML string sequencing
operations on data values in the SearchSpecification object. That XML
string could be just another attribute of SearchSpecification and you
would not need the Strategy pattern at all. Since the
SearchSpecification could be defined externally, you could even add new
algorithms without touching the application code because
SearchAlgorithm's semantics changes to XMLParser.

The Invariants and Parametric Polymorphism category on my blog provides
a number of interesting examples that may be of interest.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
(e-mail address removed)
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
(e-mail address removed) for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top