General Principles of Component Design
General Principles of Component Design
topics that apply to all types of ActiveX components. These chapters provide
necessary background for the in-depth treatment of component types in subsequent
chapters.
Note If you are using the Control Creation Edition of Visual Basic, some of the material
covered in this document may not be applicable. With the full editions of Visual Basic you have
the ability to create applications, ActiveX documents, and other types of components. Although
some of the terminology may relate to application specific objects such as forms, in most
cases the underlying concepts also apply to ActiveX control creation.
1
This chapter begins with “Component Basics,” a group of topics that explain key
terminology and concepts of component design.
The rest of the topics in "General Principles of Component Design" and “Debugging,
Testing, and Deploying Components” are organized according to the general
sequence of development tasks for components:
1. Determine the features your component will provide.
2. Determine what objects are required to divide the functionality of the component
in a logical fashion.
1See “Adding Classes to Components.”
3. Design any forms your component will display.
4. Design the interface — that is, the properties, methods, and events — for each
class provided by your component.
2See “Adding Properties and Methods to Classes,” “Adding Events to Classes,”
“Providing Named Constants for Your Component,” “Providing Polymorphism by
Implementing Interfaces,” and “Organizing Objects: The Object Model.”
3The remainder of the task-oriented topics are contained in “Debugging, Testing,
and Deploying Components.” In addition to the following development tasks, they
cover distribution, version compatibility, and creating international versions of
your component.
5. Create the component project and test project.
6. Implement the forms required by your component.
7. Implement the interface of each class, provide browser strings for interface
members, and add links to Help topics.
8. As you add each interface element or feature, add code to your test project to
exercise the new functionality.
9. Compile your component and test it with all potential target applications.
1
Contents
· Component Basics
· Adding Classes to Components
—1
Component Basics
A software component created with Visual Basic is a file containing executable code
— an .exe, .dll, or .ocx file — that provides objects other applications and
components can use.
An application or component that uses objects provided by other software
components is referred to as a client. A client uses the services of a software
component by creating instances of classes the component provides, and calling their
properties and methods.
In earlier versions of Visual Basic, you could create components called OLE servers.
The features of components created with Visual Basic are greatly expanded, including
the ability to raise events, improved support for asynchronous call-backs, and the
ability to provide ActiveX controls and documents.
What’s in a Name?
The names you select for your class modules and for their properties, methods, and
events make up the interface(s) by which your component will be accessed. When
naming these elements, and their named parameters, you can help the user of your
component by following a few simple rules.
—2
—3
—4
—6
—7
—8
7
The IFinance and IFinance2 interfaces are defined in separate type libraries, which
are referenced by the components that implement the interfaces (in this case, versions
1.0 and 2.0 of Finance.dll), and by the applications that use the interfaces (Payroll
and GeneralLedger).
System evolution is possible because future applications can take advantage of new
interfaces. Existing applications will continue to work with new versions of
components, as long as the components continue to implement the old interfaces as
well as the new.
—9
Name Property
Choose class names carefully. They should be short but descriptive, and formed from
whole words with individual words capitalized — for example, BusinessRule.
The class name is combined with the name of the component to produce a fully
qualified class name, also referred to as a programmatic ID or ProgID. For example,
the fully qualified class name of a BusinessRule class provided by the Finance
component, is Finance.BusinessRule.
The topic “What’s in a Name?” earlier in this chapter, outlines the rules for naming
classes, properties, and methods.
Defining Interfaces
The default interface for a class is composed of the properties and methods you define
for it, as discussed in “Adding Properties and Methods to Classes,” later in this
chapter.
—10
—11
—12
' Set the value for the read-only Created property when
' the object is created.
Private Sub Class_Initialize()
mdatCreated = Now
End Sub
—13
—14
—15
—16
—17
—18
—19
12
Tip You might think that you could save space by declaring the internal variable
mdowDayOfWeek As Byte instead of As VbDayOfWeek — since the latter effectively
makes the variable a Long. However, on 32-bit operating systems the code to load a Long is
faster and more compact than the code to load shorter data types. Not only could the extra
code exceed the space saved, but there might not be any space saved to begin with —
because of alignment requirements for modules and data.
31
You can make the members of an enumeration available to users of your component
by marking the enumeration Public and including it in any public module that
defines a class — that is, a class module, UserControl, or UserDocument.
When you compile your component, the enumeration will be added to the type
library. Object browsers will show both the enumeration and its individual members.
Note Although an enumeration must appear in a module that defines a class, it always has
global scope in the type library. It is not limited to, or associated in any other way with the class
in which you declared it.
32
General Purpose Enumerations
The members of an enumeration need not be sequential or contiguous. Thus, if you
have some general-purpose numeric constants you wish to define for your component,
you can put them into a catch-all Enum.
Public Enum General
levsFeetInAMile = 5280
levsIgnitionTemp = 451
levsAnswer = 42
End Enum
33
—20
—21
—22
—23
—24
An Interface is a Contract
When you create an interface for use with Implements, you’re casting it in concrete
for all time. This interface invariance is an important principle of component design,
because it protects existing systems that have been written to an interface.
When an interface is clearly in need of enhancement, a new interface should be
created. This interface might be called Interface2, to show its relationship to the
existing interface.
While generating new interfaces too frequently can bulk up your components with
unused interfaces, well-designed interfaces tend to be small and independent of each
other, reducing the potential for performance problems.
Factoring Interfaces
The process of determining what properties and methods belong on an interface is
called factoring.
In general, you should group a few closely-related functions on an interface. Too
many functions make the interface unwieldy, while dividing the parts of a feature too
finely results in extra overhead. For example, the following code calls methods on
three different interfaces of the Velociraptor class:
Public Sub CretaceousToDoList(ByVal vcr1 As _
Velociraptor, ByVal vcr2 As Velociraptor)
Dim dnr As IDinosaur
Dim prd As IPredator
vcr1.Mate vcr2
Set dnr = vcr1
dnr.LayEggs
Set prd = vcr1
prd.KillSomethingAndEatIt
—25
—26
End Sub
End Sub
49
Notice that the argument of the Attack method uses another interface, IDinosaur. One
would expect this interface to contain methods describing general dinosaur behavior,
such as laying eggs, and that it would be implemented by many classes —
Velociraptor, Tyrannosaur, Brontosaur, Triceratops, and so on.
Notice also that there’s no code in these methods. IPredator is an abstract class that
simply defines the interface (referred to as an abstract interface). Implementation
details will vary according to the object that implements the interface.
For example, the Tyrannosaur class might implement IPredator as follows:
Implements IPredator
—27
—28
—29
—30
Figure 6.5 A flat object model with several externally creatable objects
—31
—32
—33
—34
17
The Bicycle object would have a Frame property that returned a reference to its
Frame object. The Frame object would have a FrontWheel and BackWheel property,
each of which would return a Wheel object. The Wheel object would have a Spokes
property that would return a Spokes collection object. The Spokes collection would
contain the Spoke objects.
You may also have dependent objects that are used internally by classes in your
component, and which you do not want to provide to users of your component. You
can set the Instancing properties of the class modules that define these objects to
Private, so they won’t appear when users browse your type library.
For example, both the Frame and Wheel objects might have collections of Bearing
objects, but there may be no reason to expose the Bearings object, or the collections
containing it, for manipulation by client applications.
Important In order to keep the Bearings property from appearing in the type library, you must
declare it using the Friend keyword, as described in “Private Communications Between Your
Objects,” earlier in this chapter.
65
The Simple Way to Link Dependent Objects
Frequently it makes sense for a complex object to have only one instance of a
dependent object. For example, a Bicycle object only needs one Frame object. In this
case, you can implement the linkage as a simple property of the complex object:
Private mFrame As Frame
—35
—36
—37
—38
—39
—40
19
Client application B has released its reference to its Widget object. The Widget object
has a reference to a Knob object, whose Parent property refers back to the Widget
object, keeping both objects from terminating.
A similar problem occurs if the Widget object contains a collection of Knob objects,
instead of a single Knob. The Widget object keeps a reference to the Knobs collection
object, which contains a reference to each Knob. The Parent property of each Knob
contains a reference to the Widget, forming a loop that keeps the Widget object,
Knobs collection, and Knob object alive.
The objects client B was using will not be destroyed until the component closes. For
example, if client A releases its Widget object, there will be no external references to
the component. If the component does not have any forms loaded, and there is no
code executing in any procedure, then the component will unload, and the Terminate
events for all the objects will be executed. However, in the meantime, large numbers
of orphaned objects may continue to exist, taking up memory.
—41
—42