advice for a class implementation? ( ugh, long... )

C

Corey

Hello -

Ok, as I'm getting up to speed w/ ruby, I decided to write an absurdly
over-featured quote generator.

My question is more of a general OOP design issue, hope no
one minds! I'll try to keep things terse. [ sorry... this post ended up
becoming _way_ more involved than I anticipated... yikes. ]

First, the purpose of this program is to provide lots ( and lots... ) of
control over the maintainance and interaction of a collection of quotes.

I decided to use xml so I could get some experience w/ rexml.
( I'll later be expanding the functionality to a sql db store as well. )

Here's an example of my xml representation of a quote:

<quote id='3'>
<attribution>
<source>Primus</source>
<origin medium="song">Frizzle Fry</origin>
</attribution>
<quotation style="verse" category="social" subject="tradition">
To defy the laws of tradition -
is a crusade, only of the brave.
</quotation>
<metainformation>
<url>http://lyrics.rare-lyrics.com/P/Primus/To-Defy-The-Laws-Of-Tradition.html</url>
<comment>blah blah blah</comment>
</metainformation>
</quote>

( yeah, I know... but remember this is _purposefully_ over-engineered )

So, after some thought I figured I have two classes here: "Quote", and
"QuoteCollection". ( a single Quote doesn't need an 'id' attribute, but a
Quote within a QuoteCollection does. )

A Quote obviously consists of nothing more than the information/attributes
which I have defined to represent a single quote object - it needs to be
responsible for:

1- generating brand new quotes
2- accessing each attribute
3- writing/modifying each attribute
4- instantiating new quotes pulled from a QuoteCollection
(here's the problem, see further down below)

A Quote's attributes/accessors would be the following:

quote.source
quote.origin
quote.attribution
quote.medium
quote.style
quote.category
quote.subject
quote.quotation
quote.url
quote.comment


Then I have the QuoteCollection ( it'll be Enumerable ) class, which is
exactly what it says it is - being responsible for the following:

1- selecting random quotes
2- allow searching of quotes
3- modifying existing quotes
4- inserting new quotes
5- removing quotes
6- viewing all authors, sources, mediums, origins, categories & subjects
( for increased assistance in searching )

A QuoteCollection would contain many individual quotes, each marked up
in xml; an extra attribute, 'id', would be necessary for a QuoteCollection
Quote.

A QuoteCollection's attributes/accessors would be the following:

qc.authors
qc.sources
qc.mediums
qc.origins
qc.categories
qc.subjects
qc.quote

( qc.quote would be a Quote object, whichever one was currently being
operated on; so most/all the QuoteCollection methods would be
passing around and operating on actual whole Quote objects, ( as
opposed to quote xml strings, or single Quote/quote attributes/values ) )


SO, my question concerns how best ( or most proper ) to internaly
represent a Quote object within the Quote class...

Since I opted to go the route of using an xml file for the persistence of
QuoteCollections, it would seem natural that the Quote class would
represent Quotes as xml also -- but the problem is that I intend to
later extend QuoteCollection to be able to use either an xml file
backend _or_ an sql database backend.

I'm confused because I'm thinking that a Quote is simply a collection
of a particular set of attributes which expresses what I have defined
to be a quote - the format which I decide to persist those attributes
shouldn't be relevent (certainly shouldn't be hardcoded/assumed - for
I later expand QuoteCollection to use either an xml file _or_ a sql db
backend ).

Thanks for bearing with me so far... I hope I can word the rest so
that you know what the hell it is that I'm on about! <grin>

How should an xml QuoteCollection interact with a Quote object?

They're tightly coupled: I believe I'll be mixing-in Quote into
QuoteCollection, and QuoteCollection will be returning and dealing
with individual Quote objects... so I guess when a QuoteCollection
is instantiated in order to, say, modify an individual quote - that it
needs to pull this xml'ified quote and then make an actual Quote
object out of it, so that it can then be edited ( see #2/3 of the
Quote responsibilities above ), after which this modified Quote
instance/object would of course then need to be inserted back
into the QuoteCollection - which means it would need to be
re-translated back into xml form... upon which is looses it's
"Quote'iness"??

This all seems horribly inefficient and awkward... I realize that I'm
missing something, and that I'm definitely making it more complex
than it needs - but I'm pretending that this is a "professional grade"
api, so I'd like to approach and implement it in the same way an
expert/professional would, which is why I'm abstracting Quote
from QuoteCollection; pretending that a Quote would be re-usable
in a variety of different cases -- not only within the context of
an xml-file-persisted program, and in fact not necessarily within
the context of xml whatsoever.

So how should I internally represent a Quote object?

Should I represent Quote's as a hash or struct, and define
a 'xml_to_quote' and 'quote_to_xml' method? Where would
these methods logically go, in Quote or QuoteCollection?

Should I just go and make it a trait of Quote's to be xml? i.e.,
define a "quote" as not only a particular collection of attributes,
but rather a particular collection of attributes which are marked
up in xml? Remember that later on ( for further learning ) I intend
on allowing a QuoteCollection to be persisted in a sql backend,
so such a requirement would seem strange - I'd be stuck doing
the same sort of awkward/inefficient translation between simple
data/strings and xml.

Maybe I need an abstract class?

If you've actualy followed me this far ( amazing! ) - thanks!

All clues/hints/advice very much appreciated, thankyou for your time!

Phew...

Corey
 
F

Florian Gross

I think the problem is that you want to make the SQL adapter more
efficient than just pulling all the Objects out of the database and then
operating on them in RAM. I'd suppose you want to use SQL queries here
instead. I would expect the problem space to get a lot simpler if you'd
decide to use a prevalence system instead.

If you really want to use a SQL backend, this might work:

Have QuoteCollection::SQL and QuoteCollection::XML both implementing the
same interface that always return Quotes. I don't think an abstract base
class (QuoteCollection::Base) would make much sense here because it
doesn't seem like there's any functionality that those classes would share.

If you would base your application on a prevalence system you would just
need to provide different constructors (.from_xml and .from_db) for one
single QuoteCollection class that just uses Quotes internally.

I'm not sure if this is the best or most professional way of doing
things, but I would expect it to work.
 
C

Corey

I think the problem is that you want to make the SQL adapter more
efficient than just pulling all the Objects out of the database and then
operating on them in RAM. I'd suppose you want to use SQL queries here
instead.

Affirmative.


I would expect the problem space to get a lot simpler if you'd
decide to use a prevalence system instead.

I'm not quite sure what you mean by prevalence system.

If you really want to use a SQL backend, this might work:

I would like the ability to be able to choose backends - whether it be
an xml file, a sql db, or even a yaml file or an oodb. My purpose behind
this being that it makes for a more difficult project - as this whole task
is merely an assignment to myself to jump into ruby as thoroughly as
possible. So, I want the datastore to be abstracted/plugable, and as
transparent to the user as possible.

Have QuoteCollection::SQL and QuoteCollection::XML both implementing the
same interface that always return Quotes. I don't think an abstract base
class (QuoteCollection::Base) would make much sense here because it
doesn't seem like there's any functionality that those classes would share.

Yes - I think you're right. But what's adding to my confusion is this:

Though the various backends/datastores ( QuoteCollection::#{persistence} )
won't share any actual implementation - I _do_ want to ensure that they will
all share the same _interface_... I want them all to have the same public
methods ( though, of course, not necessarily the same private methods or
the same public/private attributes ) so that from the user's perspective, it
will appear that they in fact do have the same functionality. More about this
further down below.

This is why I had the notion of a base class for QuoteCollection. I guess
there is no way in ruby for me to formally enforce an "Interface" that all
QuoteCollection::#{persistence} classes must share.

That being said, in response to an earlier post of mine, it was suggested
that I do things in the ruby-way rather than try too hard to bring old habits
from other languages. I totally agree with that advice - my aim here is to
understand/learn the ruby idioms.

If you would base your application on a prevalence system you would just
need to provide different constructors (.from_xml and .from_db) for one
single QuoteCollection class that just uses Quotes internally.

Thanks for your advice here - this suggestion of yours, as well as your
first suggestion to create QuoteCollection::SQL and ::XML classes was
very helpful and got me to thinking.

After pondering over all this, I realized that I was definitely missing
something that would allow all these pieces to work together. Turns
out there were two things.

I believe I was trying to overload the QuoteCollection class with more
functionality that it should be responsible for. So, I've decided to first
of all make the QuoteCollections extremely stupid - a QuoteColection
itself only knows that it's a container for something, namely quotes.
A QuoteCollection has no insight into exactly what a Quote is, only
that it has one or more of them, and it only deals with them under its
own native storage format ( xml, sql, yaml, etc ) - it is only able to
add them, remove them, iterate through them, and dispense them....
it's relegated to being nothing more than a sort of Quote filesystem -
it doesn't try to be an editor or pager.

Then of course I have the Quote class - which is _really_ stupid:
being nothing more than a collection of attributes which describe
a quote. A Quote requires only two of its attributes to be populated
in order to be a Quote, namely @quotation and @source, every other
attribute is merely optional meta-data. A Quote only contains state, it
has no behavior ( unless you consider instantiation and attribute
accessors to be behavior. ) -- it has no concept of persistence, or
various data formats, or anything.

So what I need now is a "QuoteMachine" class... it provides the
necessary interface between Quotes and a QuoteCollection. It will
be through the instantiation of a QuoteMachine by which the means
of normalized interaction/access to any particular QuoteCollection
will be performed. This will be how the user searches Quotes, and
displays Quotes, removes/adds/modifies Quotes, etc - the
QuoteCollection backend and the Quote class itself will be totally
transparent... QuoteMachine will have .from_collection and .to_collection
private methods that will fullfill the roll of translating a Quote object
in/out from a QuoteCollection::#{persistence} data format.

Finally, there will be various frontends for actual end-users ( rather
than programmers ); i.e. I'll start with a simple command line interface,
then I'll tackle a web-based frontend in order to get me up to speed
on Rails ( no pun intended ).


Does this sound like a reasonable/workable model for what I'm trying to
accomplish?

Any hints/tips/advice regarding this design, or should I just shut-up
already and start hacking! (c8=

I'm not sure if this is the best or most professional way of doing
things, but I would expect it to work.

Thanks for the assistance!


Beers,

Corey

--

Every man who puts money into the hands of a "government" (so called),
puts into its hands a sword which will be used against himself, to
extort more money from him, and also to keep him in subjection to its
arbitrary will.
- Lysander Spooner,
'No Treason. No. VI, The Constitution of No Authority'
 
F

Florian Gross

Corey said:
I'm not quite sure what you mean by prevalence system.

http://madeleine.sourceforge.net/ is a good example of one.
Though the various backends/datastores ( QuoteCollection::#{persistence} )
won't share any actual implementation - I _do_ want to ensure that they will
all share the same _interface_... I want them all to have the same public
methods ( though, of course, not necessarily the same private methods or
the same public/private attributes ) so that from the user's perspective, it
will appear that they in fact do have the same functionality. More about this
further down below.

You can use test cases for this:

require 'test/unit'
class QuoteCollectionTest < Test::Unit::TestCase
def initialize(qc, *more)
@qc = qc
super(*more)
end

def run(result)
if self.class != QuoteCollecionTest then
super
end
end

def test_store_and_fetch_all
quote = Quote.new:)author => "Foo", ...)
@qc.store(quote)
assert_equal(
, @gc.fetch_all)
end

...
end

class QuoteCollectionYAMLTest < QuoteCollectionTest
def initialize(*more)
super(QuoteCollection::YAML.new("filename"), *more)
end
end

and so on.
After pondering over all this, I realized that I was definitely missing
something that would allow all these pieces to work together. Turns
out there were two things.

[simplify QuoteCollection and Quote classes]
So what I need now is a "QuoteMachine" class... it provides the
necessary interface between Quotes and a QuoteCollection. It will
be through the instantiation of a QuoteMachine by which the means
of normalized interaction/access to any particular QuoteCollection
will be performed. This will be how the user searches Quotes, and
displays Quotes, removes/adds/modifies Quotes, etc - the
QuoteCollection backend and the Quote class itself will be totally
transparent... QuoteMachine will have .from_collection and .to_collection
private methods that will fullfill the roll of translating a Quote object
in/out from a QuoteCollection::#{persistence} data format.

But this isn't compatible with the want of an optimized SQL adapter. You
couldn't use SQL for searching through quotes for example.

I guess the best way would be to let each QuoteCollection class
implement the bare minimum of functionality you described above and add
everything else around that as methods to your QuoteCollection::Base
class. This would make it easy to add a new adapter (for example for
YAML) while still allowing to optimize specific cases that can be
implemented faster than by using the primitive methods. (like searching
for specific quotes)

class QuoteCollection::Base
def find_by_author(author)
fetch_all.find_all do |quote|
quote.author == author
end
end
end

class QuoteCollection::YAML < QuoteCollection::Base
def initialize(filename)
@quotes = YAML.load(File.read(filename))
end

def fetch_all
@quotes
end
end

class QuoteCollection::SQL < QuoteCollection::Base
def initialize(backend)
@backend = backend
end

def fetch_all
@backend.execute("SELECT ALL FROM quotes")
end

# Overridden, more efficient implementation
def find_by_author(author)
@backend.execute("SELECT ALL FROM quotes WHERE author = '%s'",
author)
end
end

This all being heavily simplified of course.
 
J

John Wilger

Though the various backends/datastores ( QuoteCollection::#{persistence} )
won't share any actual implementation - I _do_ want to ensure that they will
all share the same _interface_... I want them all to have the same public
methods
This is why I had the notion of a base class for QuoteCollection. I guess
there is no way in ruby for me to formally enforce an "Interface" that all
QuoteCollection::#{persistence} classes must share.

That being said, in response to an earlier post of mine, it was suggested
that I do things in the ruby-way rather than try too hard to bring old habits
from other languages. I totally agree with that advice - my aim here is to
understand/learn the ruby idioms.

The most "transparent" way of doing things that I can think of that
would ensure the same interface across all of the implimentations
would be to have a single QuoteCollection class that would be used no
matter what the actual backend was (which would ensure that you are
providing the same interface. Then you could define a configuration
file (in YAML?) that would specify which backend to use (and any
necessary meta-data about the backend, i.e, login info). The
implimentation details for the various backends could be defined
inside modules, and the QuoteCollection class could dynamically
include the appropriate module depending on the configuration setting.

--
Regards,
John Wilger

-----------
Alice came to a fork in the road. "Which road do I take?" she asked.
"Where do you want to go?" responded the Cheshire cat.
"I don't know," Alice answered.
"Then," said the cat, "it doesn't matter."
- Lewis Carrol, Alice in Wonderland
 

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

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,239
Members
46,827
Latest member
DMUK_Beginner

Latest Threads

Top