We will see two types of interfaces: governing and
interacting.
Governing Interfaces
Governing means that the interface outlines a set of methods
which the inherited class must implement. Certain functionality is guaranteed
in those classes which implement it. Everyone is fairly familiar with this
concept.
Interacting Interfaces
Interacting interfaces are those which allow
interchangeability of the classes which implement their signature. Instead of
interacting directly with the product classes which implement the interface, we
interact with the interface. The client will call a factory method which
returns a product-class, but because the product is wrapped in the
interacting-interfaces, we can interact with the product class through the
interface instead of directly.
We see this in the workplace. When the software engineer
needs new tables added to the database, she does not send a request to “David.”
Instead, she sends a request to the DBA. DBA is the interacting interface and
when “David” leaves the job, the HR Manager will replace him. So the Software
Engineer will send the same requests to the new DBA, regardless of who is
acting in that position.
It not only saves us from having to have knowledge of the
classes with which we interact, but also the individual names of methods we
need to call. All of that is standardized through the interacting interface.
It is obvious that interacting interfaces subsume governing
interfaces. For one thing, there is no interface that does not govern the
classes which implements it. So then, all interfaces are governing interfaces.
The second reason is that for the client to interact with the product class as
the interface, it must be governing the behaviour of the product class to
ensure that the client has a uniform set of methods which can be invoked,
regardless of the concrete-class hiding behind the interface.
Containable Object Groups (COGs)
As an aside, we have a famous expression in Jamaica, about being given a “basket to carry water.” It literally means that you were
given the wrong tools to do the job and you are generally ill-equipped to
perform what needs to be done. The literal translation is that “if you want to
transport water, you need the right container to carry it in.”
We have a similar situation in object oriented design, but
we are not as interested in the transportation mechanism as we are in what is
being transported. As a corollary to the saying, we could quip, “Before you
carry water in a basket, make it into ice first.” This water is more easily
transportable.
Now to realize the full intent of object oriented programming
and design patterns, let us fine-tune the analogy a bit. When you are serving
drinks to guests, you do not want to be breaking ice to put into every glass
you pour. So you use an ice tray. Let us say you have an ice tray with 2 cube-holders.
You can pour spring water into one cube-holder and club soda into another.
Depending on the tastes of the drinker, you now have
contained-object-groups: ice cubes with specific ingredients. Because they are
solid, you can extract them neatly from the ice tray and drop them in the
drink. But suppose your guest takes a sip and changes his mind about their
choice of drink. You look into your freezer and realize you are fresh out of
ice cubes. No problem mon! Just pour the drink out of the glass, wash off the
ice cubes and pour a different drink. Because the ice cubes are frozen and solid,
they maintain their form irrespective of the surrounding drink. Thus they are
highly reusable.
However, if you mixed your drinks with sugar, lemonade and cold
water, you would not be able to extract the cold-water out of the drink to
reuse in another drink. All the contents would be so intermingled that it
would be impossible to re-extract the cold water.
This brings us to another point. It is not really the water
in the ice cube that we are interested in is it? It is really the “cold.” The
water is really a delivery mechanism for “coldness.” So the “coldness” is
bound up with the water in order to be useful, since we cannot really deliver
pure cold (at least not immediately). In the same way, in the quest for
object-reuse we tend to find that the ideal of having perfectly reusable
objects is not attainable. Many times there is some level of
object-intermingling within our code since the ability of individual objects to
perform useful tasks by themselves is very limited.
The promise of object-oriented software was code-reuse. In
reality, because of the way in which objects are instantiated or refer to each
other, code quickly becomes un-portable. Without uprooting a lot of other
surrounding classes, we cannot really achieve the code reuse we desire. This
brings us to the concept of Containable-Object-Groups. These are sets of
objects which can be lifted out of our code, packaged into a library of
functions and reused by several clients. All this is in a manner similar to
lifting a whole ice cube out of a drink and dropping it into another.
From studying design patterns I have come to the conclusion
that most of the time code reuse will not come about by trying to extract
individual objects and reusing them. Really useful code tends to have objects
that have some knowledge of other objects. However, reuse can be attained at
the COG level by packaging the interchangeable classes, the interface they
conform to and the factory class which selects among them for return to the
client. The COG concept is really a repackaging of an idea that most folks
already know as black-box-reuse. You can think of COG's as the black-boxes in
design patterns. Even so, they are not black-boxes in the pure sense since
their code has to be adjusted to the situation they are being adapted to.
COG's in code are similar to cog-wheels in real life. A
cog-wheel is made up of the inner-hub which allows it to rotate on a spindle (a
row of teeth which will spin against another cog-wheel and the body of the cog
which connects the teeth to the hub). In the same way that we cannot reuse the
cog’s teeth by themselves, we tend not to be able to reuse individual objects
by themselves. However, design patterns allow us to consciously design our
objects in containable-object-groups, which allow for a high level of reuse.
In the sample code you will see the reusable COG's in
namespaces and assemblies. Usually the COG is separated from the client and
the client has a reference to the COG.
Delivery Mechanisms
Within each containable object group, we attempt to draw an
imaginary line around the classes in the group and isolate them from the
outside world. We then make the product classes friends of the classes in the
group, but private to everyone else. We also introduce a delivery mechanism
into the group. This delivery mechanism is responsible for delivering the
product classes (the classes that do work) to the client code. Deliver
mechanisms are of two main forms, active and passive.
Passive Delivery Mechanism
A passive delivery mechanism is usually an
interacting-interface with which client code will interact. Instead of
instantiation classes as themselves, clients instantiate them as the parent
interface which they implement. This frees the client code from having to know
which class it will use until run time.
Active Delivery Mechanism
An active delivery mechanism is usually a factory class
which accepts a parameter from the client and returns the appropriate class to
the client. This allows us to add new classes and modify existing classes
easily, since the factory will worry about how to best translate the requirement
stated by the client into an object that performs in the specified way.
Active delivery mechanisms include passive delivery
mechanisms because the factory will return the class as the interface, thereby
maintaining transparency.
Broken Ice-Tray
In the days when procedural code ruled, the antithesis to
well structure code was spaghetti code which allowed the execution to jump all
around in the code. In my readings I have not seen a term in object-oriented
development which mirrors the concept of spaghetti code. So again, being the
overly-brave person I am, I will attempt to coin the "Broken-Ice-Tray"
concept.
An ice tray which is broken allows water to freeze in an un-partitioned
manner and thereby prevents the easy separation of the ice cubes. Object
oriented code that has classes that break encapsulation by embedding a lot of
knowledge about each other I refer to as a “Broken-Ice-Tray.” You could also
call it the "Pot-of-Ice."
Extra-Pattern-Issues
Some issues have little to do with the topic at hand (design
patterns). An example is the use of an oledbcommand object with a datareader
configured to return a single row versus using an oledbcommand object with an
SQL which returns a single row.
On the other hand, substituting a MustInherit (abstract)
class for an Interface is generally a pattern issue.
Intra-Pattern Issues
Intra-Pattern Issues are issues which are internal to the
pattern. By default the issues are intra-pattern and so the term is hardly
used.
Inter-Pattern Issues
These are issues which affect the way in which other
patterns would interact with this pattern and aid in its operation. Because
the other pattern is named in the inter-pattern discussion, the term
“Inter-Pattern issue” is hardly used. It is obvious that the mention of
another pattern amounts to the same thing.
Pattern-Safety
This speaks to the issue of the recognizability of the
pattern. Imagine you have a problem with your jet-ski and you bring it to your
trusty car-mechanic who has worked on cars for 20 years. An engine is an
engine, right? But he may or may not understand how to fix it because there is
a chance he may not recognize the components and how they fit together to make
the jet-ski work. This is why you have people who fix jet skis and those who
fix cars.
That is the risk you run when you spice up a pattern, cut an
interface or add a product-class. Code maintainability hinges on people being
able to look at your code and quickly see the pieces and how they interact. Patterns
give you that predictability and recognizability. But if you modify the
pattern because at a particular point in time one of the participants was not
critical to its function, then you lose pattern safety. Pattern safety is the
guarantee that other developers who have expertise in design patterns will be
able to maintain and extend the code or reuse the COG's in the pattern (see
definition above). Developers who are responsible for keeping your code
running may be in for a warm and unpredictable time if you carve up the pattern
arbitrarily.
Achievement of pattern safety is one of the reasons I hope
that there will be pattern standardization committees who work towards
solidifying how the patterns should look and feel in all the major languages,
along with all the acceptable variations (with a name and code for each
variation).
Conclusion
I hope you found this article beneficial in regards to the
terms you should know when using patterns in VB.NET.