(5) Extensibility
Extensibility is defined as the feature of an Application or Module to
accommodate increased functionality with minimal impact to the existing
codebase or functionality, and with increased re-usability.
Extensibility depends directly on Modularity.
Code should be designed in the form of individual blocks, each of which
performs its distinct atomic function. This simply means that we should
refrain from writing long procedural blocks of code that execute a
complex sequence with a lot of branching, looping and especially
control transfer. Instead, it should be broken into a set of individual
method invocations. That way, the code is readable and broken down into
segments and if there is a need to accommodate another step, or
different functionality, it is possible to do so by injecting another
method call and adding a new method as opposed to injecting a block of
code to a pre-existing block of code.
In addition to modularity, Inheritance
is critical to sharing code across a set of classes without having to
incorporate the shared code in each module. One should implement
functionality that is shared by numerous classes in a base class. It
may not be possible for every class to have multiple base classes, in
which case using of strongly typed interfaces is recommended.
For example, if we want each screen in our application to support
hibernation – the ability to persist the application to disk and
take off at any point, it becomes critical that the Screen classes
(Forms) in the application implement an interface that contains a
Serialize method (like ISerializable) which forces every screen to
implement the Serialize method, thus enforcing the ability to persist
to disk. It may be impossible to ensure that the implementation of
serialization in our example is going to work. The only way to ensure
that is to govern the Unit test cases and code itself using the
enforced SDLC Steps as we outlined earlier in this document.
We shall illustrate this with a real life example. Let us say that we
have a Customer class in our application which implements a Search()
method that returns an array of CustomerDetail class
instances based on valid search criteria. Now imagine that you
have extended your application to also enable search in a business
partner's database. Example being the automotive industry. Your web
site currently services GM customers. Now, all of a sudden BMW and
Mercedes want you to handle their customers also. You would create a
generic Customer base class and create three specialized classes -
GMCustomer, BMWCustomer and MercedesCustomer, all three implementing
the Search() method against the specific database. Again BMW may not
want to share the data with you, but might want you to access their web
service instead, while GM offers a data feed into your table. One
Behavior - Multiple implementations. With inheritance, you can make
this happen. Your application can continue using a reference to the
Customer class and not have to worry about the actual implementation
(extension).
The above example also illustrates Polymorphism
- The ability to implement multiple implementations of code and enable
as single invocation or instantiation to utilize different
implementations based on context. Polymorphism is illustrated in two
manners in the above class example.
Instantiation-driven: For one,
depending on the system context during construction, the application
can use a reference to the Customer class to either instantiate an
instance of the BMWCustomer, or an instance of the GMCustomer class.
This means that results can be obtained from a specific system with a
different search implementation based on system.
Note that you can also model classes to use these different
implementations of the Customer class to aggregate the results of the
customer search. What if your search screen wanted to search across
systems? This aggregation could not be achieved without the use of a
Factory Class or a specific CustomerSearch class, which in turn invoked
the search on each of the individual implementations and returned the
aggregated result in the form of an array of references of type
CustomerDetails.
Invocation-Driven: Understand
that the Search method itself can contain several specific
implementations depending on the order and type of arguments used -
Method overloading. It is obvious that the valid return type for all
these search implementations should be an array of references to the
type CustomerDetails, but depending on the arguments, different method
implementations will be invoked. This is the second type of
polymorphism that the above example demonstrates.
While there are multiple implementations of Customer, and there are
multiple ways to implement this search, based on parameters, some
criteria remain common to all the implementations by class and by
method. This is where inheritance promotes reusability. You can
implement common functionality like connecting to the database (ODBC),
retrieving the connection string, decrypting data, etc into the base
class Customer, thus enabling every implementation of the class to not
worry about implementing the base features. This can greatly increase
productivity.