|
Hello Buck,
You wrote:
>I'm working toward the "ah-HA!" here, and I deeply appreciate the
>discussion to date...
>I'm missing the advantage of the extra layer of abstraction involved in
>using interface Noisable. Since every class that implements Noisable
>will be required to have a makeSound() method anyway, what does the empty
>interface bring to the table?
Jumping in here ...
Extra levels of abstraction usually help clarify the design but
specifically the advantage here is that the caller (i.e., the user of a
class that implements Noisable) doesn't need to know at compile time the
particular Noisable type that will be used at run time. It just creates
instances of Noisable and invokes the makeSound() method on them. The
result is simpler cleaner code.
Similar behaviour may seem possible by creating instances of a Noisable
class but since the implementation is still likely to be done in
subclasses you are likely to find that you will at some point need to
downcast Noisable to the actual class being used. That's messy and means
the caller needs to know the actual type. Interfaces avoid that mess.
The using class will certainly need to load the appropriate instance of a
Noisable object at run time (a class that implements Noisable) but the
name of that class can be stored in a properties file and loaded at run
time via ClassForName. Because the class is a Noisable object no casting
is necessary.
You could also do this by creating a class called Noisable instead of an
interface and that choice is really a design decision. There are three
approaches:
1/ A concrete Noisable class with a default implementation of
makeSound() which might simply make the speaker buzz.
2/ An abstract Noisable class with an abstract makeSound() method
which must be overridden by a subclass.
3/ A Noisable interface with an implicitly abstract makeSound()
method which must be implemented by an implementor.
Which do you choose? Well, if you can provide a default implementation
then creating a concrete class makes sense. The class can be instantiated
and will work on all systems that have a speaker. Specific sound hardware
can supply a subclass of Noisable that overrides makeSound() to fully
utilise the hardware.
However, not all systems have a speaker (a certain black box springs to
mind :) so maybe a default implementation of makeSound() doesn't make
sense. In that case we could create an abstract class containing an
abstract (i.e., empty) makeSound() method. Any subclass must provide an
implementation of makeSound(). Conceptually this is similar to the
default concrete class but the class designer doesn't have to provide a
default implementation.
Now, if the only thing the abstract class contains is an abstract method
then it probably serves no real purpose other than as a contract requiring
subclasses to implement their own version of makeSound(). If that is true
then it would make more sense to define Noisable as an interface. Using
an interface also allows the class to inherit from some other class that
is more representative of its real type.
Note: If there are other methods that can be implemented concretely (e.g.,
setVolume(), setVoice(), etc) then an abstract class with concrete methods
setVolume() and setVoice, and an abstract makeSound() method would be more
appropriate.
There are additional questions to be asked during the design to help
decide whether something should be implemented as a class or an interface.
Specifically, if the thing you are modeling has an "IS KIND OF"
relationship then it probably should be a class (concrete if you can model
all of its behaviour, abstract if you can't). However, if it simply
exhibits certain implementation specific behaviour then it probably should
be an interface.
To use more familiar objects, let's say we are modeling vehicles. We
could start with a generic Vehicle class which handles those things common
to vehicles (number of whee
, number of doors, colour, model, etc). Then
we can create a subclass called Car which models passenger vehicles. We
can create a subclass called Truck which models load-carrying vehicles.
Fairly straightforward hierarchy so far. Now, how do we model a Ute? (A
Ute, for the non-Antipodeans amongst us, is comparable to a pickup.)
A Ute exhibits traits common to both Car and Truck. Hmm ... how can an
object be two things at once? In the C++ world that may lead us onto the
perilous path of multiple inheritance and into Terra Incognita -- you
know, the bits on olde worlde mappes marked Here be Dragons!
In Smalltalk and Java we don't have the soft option of multiple
inheritance so a different approach is needed. We could say a Ute is
mostly used for carrying loads and therefore is mostly a Truck. Since it
does have some Car-like behaviour we can get that behaviour by creating a
private instance of Car inside the Ute class. We get Truck-like behaviour
by direct inheritance and Car-like behaviour by containment. We would
never expose the internal Car object iteself to users of Ute but we will
certainly have to expose some of the Car methods. If do not need to
change much of the Car behaviour this is a good approach. However, if the
Ute needs to alter much of the Car behaviour then a different approach
would be sensible. For instance, we could create a Car interface which
defines the minimum behaviour needed for an object that is "A KIND OF"
car. The Car class implements this interface and provides Car-specific
implemetations of the abstract methods. Ute still inherits from Truck but
also implements the Car interface and provides Ute-specific
implementations of the Car-like methods.
To return to the Noisable example (but I don't like that name so I'll
change it) let's say we are creating sound card drivers. The first
question is exactly what KIND of thing is a sound driver? It's really a
special case of a general device driver. So we could model the hierarchy
as:
Object
DeviceDriver
SoundDriver
Now a SoundDriver is likely to be different for different sound cards. So
each sound card would require its own implementation of SoundDriver. It
might also be possible to provide a GenericSoundDriver if there are
similarities between cards. So we could have:
Object
DeviceDriver
SoundDriver
GenericSoundDriver
CrysalSoundDriver
SoundBlasterSoundDriver
EssSoundDriver
but I think it unlikely that vendor specific sound drivers could truly
inherit from a generic sound driver and I also think that the SoundDriver
class is likely to be purely abstract thus indicating that its only
purpose to describe the minimum required methods of a SoundDriver type.
If you accept the argument that the various sound drivers are just a
special case of device driver then that suggests SoundDriver ought to be
an interface and we get:
Object
SoundDriver <=== interface
DeviceDriver
GenericSoundDriver implements SoundDriver
CrystalSoundDriver implements SoundDriver
SoundBlasterSoundDriver implements SoundDriver
EssSoundDriver implements SoundDriver
This hierarchy says that the different sound driver classes are "A KIND
OF" device driver that happen to implement the SoundDriver behaviour.
They can be manipulated by a using class AS IF they are device drivers
(good for loading, initializing, and unloading the driver) and also AS IF
they are sound drivers (good for playing tunes).
Even though all the sound drivers implement methods with exactly same
signature the code in each method is likely to be quite different due to
the different underlying hardware. The really neat thing is that all the
sound driver classes can be treated in exactly the same way by the using
classes without needing to know anything about them other than that they
are a kind of SoundDriver -- and no casting is needed. Using an interface
in this manner can also clarify the intent of the design
The use of interfaces in this fashion also allow even more specialised
implementations. Assume one of our sound cards not only plays files from
disk but can communicate with a CD drive. We can easily accomodate that
behaviour by creating a new interface called CdPlayer and implement that
in addition to the SoundDriver interface:
public class CrystalSoundDriver extends DeviceDriver implements
SoundDriver, CdPlayer {}
Now our specific implementation is a DeviceDriver type that also exhibits
the behaviour of a SoundDriver AND the behaviour of a CdPlayer. It is
still a device driver and sound driver but now it can also be a CD player.
Again CD player behaviour is probably similar between different devices
but the implementation will certainly be different thus suggesting an
interface rather than an abstract class.
Many Java books treat interfaces as a means of implementing multiple
inheritance but that is a really bad way to view interfaces and nothing
other than the contract is really inherited. Interfaces are way of
specifying the intent of a design. Containment is a way of dealing with
multiple inheritance.
If you've read this far then you've probably got enough to think about and
I'll stop for the moment ... :) I hope at least some of this helps.
>If this is way too "newbie" tell me to go away and read some more. I
>will do the right thing, I promise!
Yeah, sure ... I've heard that before :) I don't regard this stuff as
'too newbie'. It is fundamental to OO. It takes some thought and
practice to begin getting it right -- and frequently a mentor to guide
you.
Regards,
Simon Coulter.
«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»
«» FlyByNight Software AS/400 Technical Specialists «»
«» Eclipse the competition - run your business on an IBM AS/400. «»
«» «»
«» Phone: +61 3 9419 0175 Mobile: +61 0411 091 400 «»
«» Fax: +61 3 9419 0175 mailto: shc@flybynight.com.au «»
«» «»
«» Windoze should not be open at Warp speed. «»
«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»«»
+---
| This is the JAVA/400 Mailing List!
| To submit a new message, send your mail to JAVA400-L@midrange.com.
| To subscribe to this list send email to JAVA400-L-SUB@midrange.com.
| To unsubscribe from this list send email to JAVA400-L-UNSUB@midrange.com.
| Questions should be directed to the list owner: joe@zappie.net
+---
As an Amazon Associate we earn from qualifying purchases.
This mailing list archive is Copyright 1997-2025 by midrange.com and David Gibbs as a compilation work. Use of the archive is restricted to research of a business or technical nature. Any other uses are prohibited. Full details are available on our policy page. If you have questions about this, please contact [javascript protected email address].
Operating expenses for this site are earned using the Amazon Associate program and Google Adsense.