Introduction
Object Oriented Analysis and Design has revolutionized the way
software developers model and design a software solution. Many later
generation languages, such as Visual Basic or Java and recently, the
Dot Net languages implemented the threads of Object Orientation into
their language structures, some are termed as object based
and some as object-oriented languages. Ever since the
development of object-oriented thinking, the whole solution
framework developed by a designer, is more or less restricted to the
individual who created the idea, or among the group of developers
who shared the merits of the solution. Then into the software realm
came the idea of Design Patterns, which, motivated by
Alexander's work in Architectural and Town Planning design patterns,
gave a scientific way to record the approach that yields an
object-oriented solution, and also paved the way for documenting
certain established patterns. Design Patterns guide your way of
thinking right from the initial design of the software - namely the
functional design and database design - and then extends its
advantage till the level of the program design in terms of the
participating classes and communicating objects that interacts with
each other to arrive at the desired solution.
What This Article Will Cover
- Application of Pattern Thinking in real time component design
- Component design for book price processing software
- Elements of Reflective Design principles
- Grouping Data through the usage of Class builder and
Collections in VB6.0
- Using GOF Form for pattern representation and documentation
(GOF stands for the Gang of Four comprising of Erich
Gamma, Ralph Johnson, Richard Helm, and John Vlissides, whose
monumental work: Design
Patterns: Elements of Reusable Object-Oriented Software,
described a template for explaining and recording the various design
patterns. This article follows that template and the same can be
found in the following website: http://hillside.net/patterns/writing/GOFtemplate.htm?WROXEMPTOKEN=983125ZaTin1Bp1LxxQjXkLBSv)
We'll also show how Patterns:
- Spell out clarity and elegance in component design
- Achieve Programming and processing efficiency
- Are about crafting a solution rather than providing a solution
- Initiate and guide the creative aspects of software
development
- Aids the Reusability of a solution
- Guides as a Rule or a template
- Makes the tacit knowledge explicit
System Requirements
This is a pure component developed in Microsoft Visual Basic with
the native MS Access 2000 backend. It can be deployed in the client
connecting to the native MS Access database, with the following
hardware and software requirements. Lesser configurations might not
give optimal performance.
Hardware: Client - Pentium III (500 MHZ Plus) with 128 MB RAM
Software: Client - Windows 98/NT/2000,Visual Basic 6.0 and
MSAccess2000
The component can also be tested in Oracle by porting the data
from MS Access to Oracle. The connection string is available in form
Load event procedure of
frmPriceCalculationProcessing.frm
The component can be tested and debugged using Visual Basic 6.0
and Visual Studio Enterprise Tools.
Pattern Name and Classification:
"Serving faster by serving together"
Intent:
To develop a component that will group the data intelligently
and distribute the processing as a chain of processes to arrive at
an elegant and modular program structure and to achieve optimal
processing efficiency.
Motivation:
The success of Design patterns is in how well the concept is
implemented in the real time application scenario. The seemingly
cryptic definitions of each kind of Design pattern, has to be
explored and eulogized in its real depth, when it comes to the real
time application of Design Pattern principles. Accordingly, the
example I have chosen for illustration has its implicit need for
applying the Design pattern principles and is woven with certain
intricate definitions and approaches which should be understood
clearly before one proceeds further towards exploring the
example.
The scenario we are going to delve into is the case of a Book
Publisher, publishing books under different categories, by different
authors. Every book published comes under a specific Book Scheme. By
Book Scheme I mean the grouping of books based on their attributes,
such as Author Category code and Book Category code. For example,
books published under the category of Science
Fiction written by Experts
would belong to a unique Scheme. The moment the books are grouped in
the various schemes the pricing information can be specified either
for an individual book, or for a scheme of books.
The Book Publisher uses the concept of Price Heads, which
means a dynamic definition of the pricing information of the books,
such as Base cost, Royalty cost and so on. In Price heads we store
the numeric value of the price, which can either come under the type
of Income (I), or Expense (E). We have to sum up all the Income price
heads to arrive at the Total Income out of the Books and sum up all
the Expenses Price heads to arrive at the Total Expenses incurred.
Certain price heads that are only informative will be stored as type
' C ' - 'conventional' price heads.
As mentioned earlier, the price heads will be specified either
Book-wise or Scheme-wise, and so the task of the component is to
arrive at the Price of all the books coming under a scheme, or for
all the schemes of the Book Publisher, by exploiting the various
options available in grouping of pricing information.
This scenario warrants the application of a pattern approach, as
opposed to the procedural approach, as the information that is
available here is collective and individualized. The following are
the tasks that the component should achieve with the aid of design
patterns:
- The component should reflect and utilize the implicit
aggregation relationship of data
- An approach that crystallizes the requirements elegantly
- The component should be organized modularly but behave
cohesively
- Visible benefits in terms of processing efficiency and
reusability
- Achieve the trade off between multiple backend hits and
caching the data
- Should serve as a testimonial for the success of design
pattern principles
Before we explore what the process component should do, let's
look into the basic database design that accompanies the scenario
given below:
The design is quite straightforward,
Table Name |
Schema Explanation |
TblBookMaster |
BookCode as the primary key. Holds all
the attributes of the Books, such as Author Category and Book
Category. Other Details like Books Printed and Sold. |
TblAuthorCategoryMaster |
AuthorCategorycode as the Primary key.
Joined to AuthorCategoryCode in
TblBookMaster as one-to-many
relationship. |
TblBookCategoryMaster |
BookCategoryCode as the Primary Key.
Joined to BookCategoryCode in
TblBookMaster as one-to-many
relationship. |
TblPriceMaster |
PriceHeadName as the primary key.
Stores the PriceHeadType as
Income(' I ') or Expense(' E ') or Conventional(' C '). |
TblBookSchemeMaster |
SchemeId as the Primary Key. A
relevant Scheme Description that describes the Grouping scheme
of Books. |
TblBookSchemeDet |
Joined to SchemeId in TblBookSchemeMaster. Spelling out the
Scheme Grouping condition in a kind of frame able SQL query
that spells out the grouping relation of Books in TblBookMaster. |
TblSchemePriceInputDet |
Joined to SchemeId in TblBookSchemeMaster. Holds the set of
Values or Formulas for the Price heads that are common for all
the Books coming under a specific scheme. Defining the
Sequence of Evaluation. |
TblBookPriceInputDet |
Joined to the BookCode in TblBookMaster. Holds the set of Values
or Formulas for the Price heads that are specific for a
particular BookCode. Defining
the Sequence of Evaluation. |
TblBookPriceDet |
Joined to the BookCode in TblBookMaster. Holds the evaluated
value for each price head for all the Books coming under the
Schemes for which the Price Processing is
done. |
Shown below are the sample records for the Books available in the
three main tables, which stores the pricing information.
TblBookPriceInputDet
This table shows the pricing entries available for the Book Code
B1108, where the various price heads
like Base Cost, Paper Cost and Printing cost that are specific for this
particular Book are specified.
TblSchemePriceInputDet
This table shows the pricing information for the Scheme Id =
5, since this Book is coming under
this specific scheme, all the price head information given for this
scheme are applicable to this Book also.
TblBookPriceDet
This table holds the consolidated information about all the price
heads for the Books, after evaluating all the values and expressions
that are specified Book-wise and Scheme-wise, for this Book code =
B1108.
Structure (Component Design):
The crucial aspect of arriving at the classes for the price
processing component stems from the elements of reflective design
principles, which advocates that the design of the component should
reflect closely the design of the database and should achieve the
trade-off between the backend hits and caching the data in the
client. Based on this principle, the classes and their hierarchy
that closely reflects the design, and for efficient processing will
be as mentioned below. Since the component is developed using Visual
Basic, one can use the Class Builder utility that comes along with
VB6.0 to create the classes, their methods and properties along with
the hierarchies.
Global Class Hierarchy
In this hierarchy we can note that:
- The ClsBookAttributes stores the
fundamental information related to each individual Books and
serves as the foundation for the Class Hierarchy
- The Book Scheme Information will be processed through ClsBookSchemeDet
- The Pricing information for a Scheme of Books will be
processed through the ClsBookSchemePriceDet
- The Price of each Individual Book will be processed through
ClsBookPriceDet
Properties of ClsBookAttributes.
Almost all the fields of the TblBookMaster are reflected here as
properties, it might seem that all the fields are not necessary, but
keeping in mind the usability of these fields in some other
resulting scenario, the properties are created. The aim is simply to
make this class as the one point base class for accessing
information related to any books available in the TblBookMaster. Once the information is
poured into an object of this class, it will serve as the primary
holder of Book Information, thereby completely avoiding further
backend hits regarding the Book Information. This strategy is also
highly recommended by Microsoft even in the latest ADO.NET
architecture.
The PriceDetArray, which is of a
type called CollBookPriceStore, is
just a collection that holds the pricing information related to the
books. The class builder allows rich dynamism in creating classes in
different hierarchies and creating properties of a rich set of data
types, that also includes any collection objects that are created in
this project.
For those who are unfamiliar with using collection objects, here is an illustration.
Suppose I have to create a collection with a set of properties, what
I do is to create a class with the relevant properties and keep it
separate - the clsIndBookPriceStore is
an example of a similar kind of class (refer to the screenshot
below). Then I select the class under which the collection should
reside, and then right-click to select the new collection option and a window (shown
below) appears. In this window I specify the name of the collection
and create it based on the base class that contains the properties -
clsIndBookPriceStore. So a collection
is a class with a defined set of properties and methods. Here,
rather than accessing the properties of the class individually, a
collection gives all the properties in a single set, which is just
the pivoted format.
The main advantage of using the collections is to have indexed
and reusable component with fully armored set of properties and
methods with the richest choice of all the data types, which Visual
Basic 6.0 supports including the User defined and enumerated data
types.
Creating a collection using Visual Basic Class
Builder
The Create New Collection option in the Class
Builder
The Collection Window.
The advantage of having the PriceDetArray property of the CollBookPriceStore type, is that it will
enable the object of the ClsBookAttributes to individually store all
the pay head details corresponding to a book, in an index based
access scope, for example: ObjBookAttribs(Bookcnt).PriceDetArray.Add
Where ObjBookAttribs is an object
of the class ClsBookAttributes and
BookCnt is the index variable and
accessing the PriceDetArray elements
through the Add method.
Similarly we have the properties for clsIndSchemePriceStore, which gets pivotized
as CollSchemePriceStore.
The ClsBookSchemeDet hosts the
GetSchemeDetails method for getting
the Book Scheme related information.
The ClsBookSchemePriceDet hosts the
GetSchemeFormulas method for getting
the Scheme related formulas.
The ClsBookPriceDet hosts the GetFormulaExecute method for executing the
Formulas and arriving at the final values.
Applying Patterns (Applicability):
Visual Basic, not being a purely object-oriented language,
applying inheritance in the true sense is not possible, as Visual
Basic supports only Interface Inheritance. What we are going to
explore here are patterns like Façade and Chain Of
Responsibility, which are woven around the concepts such as
defining a unified, higher level interface to a subsystem that make
it easier to use (Façade), and avoids coupling the sender of
a request to the receiver by giving more than one object a chance to
handle the request (Chain Of Responsibility). These patterns
are natural products of any object-oriented design using Visual
Basic, so what is elucidated here is how to use the concepts much
more powerfully and train oneself into what is called pattern
oriented thinking.
The Creation of Collection objects using the Class Builder
and embedding it in different classes acts as a natural façade, but
the power of the façade is fully exploited once index based access
is maintained across the multilevel drilling of the Object
Hierarchy. The code snippet: ObjBookAttribs(Bookcnt).PriceDetArray.Add
has a powerful approach towards organizing the data. It will be
possible to maintain the whole parent child hierarchy within the
collection object with relative indexing, so that it reflects the
kind of relationship between the data as envisioned in the database
design. What we have here is a simple example whereby there is a
one-to-many relationship existing between a particular Book in the Book master
table and the various price heads that go along with a particular
Book under consideration. The Book is referred to using the index and
accessed sequentially. Certain Price
heads, such as Income and Expenses are maintained in the parent level
itself, which holds a cumulative value of all the Income and Expenses type price heads that go along with
that Book. The option to exploit all
the advantages of a multi-level parent-child relationship of data is
made possible by declaring a property of a type collection. Here the
Collection object, which is a natural
façade, assumes significant importance for the application to store,
collate, access, process and retrieve the data.
The next most important pattern that is applied here is the
Chain Of Responsibility, just as the Façade
Pattern (explained above) deals with storing and accessing
the data, the Chain of Responsibility allows easier and
effective communication across objects and allows us to exploit the
data aggregation options like the various grouping options of data,
more specifically here, the Scheme of
Books, to reduce the cycles of processing time.
Let's look at the overall flow of Book Price Processing:
Participants:
Collaborations:
If we look at the code of the BookSchemeProcess procedure, it calls the
FixBookAttributes procedure. It makes
the collection object ObjBookAttribs
ready with the necessary details about the books coming under the
scheme selected. 'Step - 1: Loop on the Book Schemes
For Schemecnt = 1 To rsSchemeMst.RecordCount
'Step - 2: Fetching the values for the Books coming under the Scheme,
' Calling the GetSchemeDetails method of the ObjBookScheme.
Set rsBookCodes = ObjBookScheme.GetSchemeDetails(rsSchemeMst!SchemeId)
The GetSchemeDetails method accepts
a SchemeId and returns a recordset for
all the set of Book codes coming under a particular Scheme. It
basically executes the spelled out SQL fields, coins a selection
string and executes it in the database, returning the result as a
record set. If rsBookCodes.RecordCount > 0 Then
rsBookCodes.MoveFirst
'Loop on the rsBookCodes coming under the scheme
'Step - 3: Loop on the set of Books in rsBookCodes
For Bookcnt = 1 To rsBookCodes.RecordCount
'Sub Step - 3.1: Creating an object for Scriptcontrol for evaluating
Expressions and binding it to the collection object.
The MSScriptControl, provided by
Microsoft, can be used for executing any valid VBScript statements,
especially assignments related to expression evaluation. It provides
a rich set of methods like run, addcode, executestatement and eval, which can be explored further for
runtime manipulations of VBScript statements and expressions. In our
example it is primarily used for expression evaluation. Set objScriptEval = CreateObject("MSScriptControl.ScriptControl")
ObjScriptEval.Language = "VBScript"
'Step - 4: Adding the Book Details into the ObjBookAttribs Collection object
ObjBookAttribs.Add rsBookCodes!BookCode, rsBookCodes!BookName, rsBookCodes!
BookCategoryCode, rsBookCodes!AuthorCategoryCode, rsBookCodes!AuthorNames,
objScriptEval, 0, 0
Now the Price processing task is
two-fold: To fetch the individual pricing information about the
books and evaluate and store the values of the price head; and to
execute all the pricing formulas that are relevant for a scheme of
books simultaneously, thereby achieving the advantage derived out of
object aggregation. 'Step - 5: Loop on the ObjBookAttribs, fetch the pricing formula and evaluate the formula.
For BookAttribCnt = 1 To ObjBookAttribs.Count
For BookPriceCnt = 1 To rsBookPrice.RecordCount
ObjSchemePrice.CollSchemePriceStore.Add
rsBookPrice!PriceType,rsBookPrice!PriceHeadName,
rsBookPrice!PriceValueOrExp,
rsBookPrice!EvaluatingSequence
'Step- 5.1:Calling the GetFormulaExecute method of ObjBookPrice
ObjBookPrice.GetFormulaExecute
GetFormulaExecute is the core
method that evaluates the expressions and values for the different
price heads collected for a Book using the ExecuteStatement and Eval methods of the script control, and
stores the values in the ObjBookAttribs.PriceDetArray collection for
the Book Index passed on to this method. Simultaneously the
evaluated values are updated into the database. 'Key-Step: Passing the index of the ObjSchemePrice and ObjBookAttribs
ObjSchemePrice.CollSchemePriceStore.Count, BookAttribCnt
rsBookPrice.MoveNext
Next
ProgBookProcess.Value = ProgBookProcess.Value + 1
Next
'End Loop
What we see here is a chain of objects being called. The
communication is through the corresponding reference indexes in the
collection, as the object at the other end of the chain processes
the information, the link is maintained without a break and the
final GetFormulaExecute method, after
executing the values of the price heads, updates the price head
values for the Book Index that is
initiated by the first object in the chain as depicted in the
process flow.
The subsequent step calls the GetSchemeFormulas method of the ObjSchemePrice, the rest of the
implementation is taken care of by the subsequent classes that are
embedded in the chain. 'Step - 6: Loop on the rsSchemeMst and pass the SchemeId's to the
GetSchemeFormulas method of the ObjSchemePrice.
For Schemecnt = 1 To rsSchemeMst.RecordCount
ObjSchemePrice.GetSchemeFormulas (rsSchemeMst!SchemeId)
ProgSchemeProcess.Value = ProgSchemeProcess.Value + 1
rsSchemeMst.MoveNext
Next
GetSchemeFormulas is the crucial
method that accepts the SchemeId,
fetches the pricing Formulas for the scheme. For each book in the
scheme the formulas are evaluated simultaneously. 'Step - 7: GetSchemeFormulas loops on the pricing formula details for
the Passed SchemeId.
For SchemeFormulaCnt = 1 to rsSchemePriceDet.RecordCount
Key- Step: Populating the ObjSchemePrice collection, only once for all the Books under the scheme
ObjSchemePrice.CollSchemePriceStore.Add rsSchemePriceDet!PriceType, rsSchemePriceDet!
PriceHeadName, rsSchemePriceDet!PriceValueOrExp, rsSchemePriceDet!EvaluatingSequence
If rsBookCodes.RecordCount > 0 Then
rsBookCodes.MoveFirst
'Step - 7.1 'Loop on the Recordset holding the Book Details coming
Under the scheme
For Bookcnt = 1 to rsBookCodes. RecordCount
Key-Step: Executing the Formula by passing the Price Index and
the Book Index
ObjBookPrice.GetFormulaExecute
ObjSchemePrice.CollSchemePriceStore.Count, BookIndex
rsBookCodes.MoveNext
Next
rsSchemePriceDet.MoveNext
Next
'Step - 8: The GetFormulaExecute method uses the VBScript control
Object to execute the formula statements and evaluate the price.
ObjBookAttribs(ColBookIndex).objStoreExp. ExecuteStatement
(CStr(PriceHeadName) & " = " & CStr(FormulaExp))
Amount = ObjBookAttribs(ColBookIndex).objStoreExp.Eval(CStr(PriceHeadName))
' Step - 9: The Values of the pricehead are stored in the PriceDetArray
in the ObjBookAttributes collection for the corresponding Index.
ObjBookAttribs(ColBookIndex).PriceDetArray.Add PriceType,
PriceHeadName, Amount
The values are simultaneously inserted into the TblBookPriceDet for the corresponding BookCode. 'Step - 10: Similarly the values for the Income and Expenses Pricehead are
Stored in the ObjBookAttribs, a node above as a collective price head for the Book.
ObjBookAttribs(ColBookIndex).Income = Income
ObjBookAttribs(ColBookIndex).Expenses = Expenses
Finally the values of Income and
Expenses are inserted into the TblBookPriceDet, using Insert SQL statements.
Consequences:
The solution described above using the Façade and Chain
of Responsibility patterns offers numerous advantages.
High cohesion
We see in the component design described above, all the methods
are independent in themselves; by the way they achieve their
corresponding functionality. So each class here represents a single
abstraction and they also handle (mainly) a single
responsibility.
Method |
Functionality Achieved |
GetSchemeDetails |
Fetches the complete set of Books under a scheme |
GetSchemeFormulas |
Fetches the complete set of Formulas under a scheme |
GetFormulaExecute |
Executes the Formulas and arrives at the Price head values. |
Low coupling
We have also achieved a low coupling by the way one class is
dependent on another, but all the communication is mainly through
the indexes of the collection. These indexes are not hard bound, it
is just a reference passed between the methods of the classes and
thereby the methods can also function independently by varying the
index parameters.
Even distribution of Behavior.
The catch of this component is the efficiency with which the
behavior is distributed, the even distribution acts in two ways:
- Non-focused Distribution: No object directly controls
the behavior of other objects, but each object acts as a
storehouse of all the processed information in a sequential
manner, thereby anywhere and anytime within the component, the
data can be accessed through the index reference.
- Focused Distribution: The thread of the objects
depending on the other objects is maintained throughout the chain
by maintaining the index reference that is unique and
parameterized. This enables a focused distribution, whereby each
object achieves their task and passes on the index, just like the
baton passed from one athlete to another in a relay race scenario.
Scalability:
This component is fully scalable as it only uses the COM
components. It can be deployed as a separate DLL in the server and
called as a component as and when the need arises to process the
pricing information for the books.
Processing Efficiency:
Let's look into an alternate way of processing the price by using
a procedural approach.
Using a procedural approach requires us to:
- Select the book details pertaining to each Scheme separately
- For each book, refer the Scheme details in which it falls and
collect the pricing information
- For each book refer the Book wise price inputs and collect the
pricing information
- Update an intermediate table, say the TblProcessFormulas
- Select each Book and refer to the formulas
- Execute the price formulas and arrive at the final values
The following illustration reveals the advantage of processing
efficiency using the object-orientation and pattern-based
aggregation over the Procedural
approach.
Book-wise processing:
When Price processing deals with pricing information that is
specific to an individual book, we may term it as Book-wise
processing. This is the simplest case of process execution as each
book is referred and their corresponding pricing entries are fetched
and updated. It takes the complete cycle time as taken by the
procedural approach.
Scheme-wise processing:
Here the Price processing is focused on the price entries that
are common for all the books coming under a scheme, here a single
set of pricing information will be common to a set of Books that are
available under a specific Book Pricing scheme. This kind of
grouping option should be exploited for optimal price processing by
the application. The greater the number of books grouped, the better
the processing efficiency.
Zone of Efficiency:
As explained in the two Collaboration diagrams, zone of
efficiency refers to the advantage in terms of processing efficiency
achieved by the object-oriented approach over the procedural
approach, as multiple hits to the backend and their corresponding
updates are bypassed.
Implementation
When it comes to implementation there is a famous quote from Paul
Evitts when speaking about Alexandrian Patterns:
"The solution is a balancing act; it
becomes a way of structuring the components of what has to be
crafted, so that the forces that affect them, and that they affect
in turn, are harmonized."
Even though the object-oriented approach is modular, scalable and
flexible, this approach towards component design does suffer from
the huge consumption of memory resources in the System in which it
is deployed, either in the client or in the server.
Creating a hierarchy of Collection
objects, and linear index searching also extends the time allocated
for processing. Nevertheless we can achieve a tradeoff by taking
only a set of Books from the backend
for processing, the limit might be 1000 or 2000 based on the memory
configurations, and also by specifying a richer configuration to the
client in which the component will be deployed, and go ahead to
achieve the desired functional advantages of the object
orientation.
The Sample Application
The application is a simple form with the option to select a
single book scheme, or all the book schemes, and a command button to
initiate the Book price processing.
The progress bar illustrates the time taken for the various phases
of processing.
Application Setup
1. Download the support material zip file, and it will unfurl a
set of classes and collections contained in a Visual Basic project
"ProjBookPubs ".
2. BookPubs.mdb is the MS Access database containing the
sample data, and it resides in the application path.
3. Run ProjBookPubs.exe to run the application and process
the pricing information
4. To view the code and measure the performance through
checkpoints, open ProjBookPubs.vbp and study the code,
alternatively the class hierarchy can be viewed through the class
builder.
Further Work
This is only a sample from the broad spectrum of the usage of
patterns. With reference to Visual Basic, the Class Builder can be
used far more elaborately, whereby a utility can be created to
arrive at the class hierarchy for any application design based on a
set of Pattern templates already created and stored. Usage of other
related patterns like Factory, Mediator or
Proxy can also be studied, and the emergence of ORM
(Object Role Modeling) and related ORM patterns can
also be taken as the subject for further research.
Conclusion
This article has attempted to bring out the essence of Pattern
Thinking, and its implementation using Visual Basic 6.0. More
than the technical dimension, Design patterns gives room for one's
thought to delve into what is called "three dimensional thinking",
where the rigidness of analysis and design ends and the realm of
intuition and lateral thinking starts. One can find numerous
instances of Design patterns evolving out of our own continuous work
in any language domain of the software we are involved in.
If this article stimulates other object-oriented developers
towards sharing their ideas earnestly, I will feel the achievement
of my overall objective of writing this article, and your feedback
will help me to contribute more in the allied areas of research in
the near future. There are many pattern groups involved in the study
of design patterns, and this subject still stands to be one of the
favorites of great object-orientation thinkers like Grady Booch and
James O. Coplien. |