8.5 Creating reusable objects

Creating reusable objects

Reuse is the ultimate goal of any object-oriented development effort. Unfortunately, it is also hard to achieve. Contrary to popular belief, reuse does not come "for free" with object-oriented development. Objects and classes must be created with reuse as one of the major goals. Beyond basic architectural challenges, reusable objects also need to fulfill organizational needs. Where should classes be stored? What dependencies will you encounter if a class is reused? How many additional classes will you have to include in your new application just to reuse this cool progress bar class?

Reuse a fairytale?

To make a long story short: Reuse is hard to achieve. In fact, it's so hard that even people who apply object-oriented techniques successfully, fail to reuse existing code. Over time, so many attempts to reuse code failed, that people started to doubt the possibility of reuse. In fact, reuse came to be considered a fairytale.

I don't agree.

In my experience, reuse is possible as long as you start reuse at design time (see Chapters 14 and 15). You need to make reuse one of your major design goals. In fact, by coding in Visual FoxPro you've already started to reuse; using the Visual FoxPro base classes is reuse in its purest form. As you can imagine, the Microsoft Visual FoxPro team spent a lot of time designing reusable base classes. You'll have to be equally committed to reuse as the Fox team was (and is). But before you learn how to create reusable objects, you need to know the pitfalls. Let's take a look at the reasons why attempts to create reusable code fail.

The most common reason for reuse failure is lack of a good design. Classes that are created (and methods that are added) as the need arises are unlikely to have a clean interface and to fulfill the needs of other applications. When programmers take this approach, they're in danger of navigating themselves into dead ends and facing the need to redesign. But are projects that don't allow programmers to design what they planned to do, likely to allow redesign? I doubt it. For this reason, they apply workarounds rather than looking for proper resolutions to problems, which clutters the interface even more. In fact, it typically ties different objects together. Internals that used to be encapsulated get exposed so other objects can bypass the object's interface to compensate for shortcomings in the design. All kinds of direct object interactions that don't happen with clear and well-defined interfaces is the number 1 reuse killer.

"So what's the fuss?" you say. "If I simply follow all the design guidelines described in Part 3 of this book, I'll end up with reusable code." Unfortunately, even well-designed object systems don't guarantee reuse because there are a number of logistical issues concerning your implementation. Recently, I tried to reuse a behavioral class that was part of an object system that was designed to be reused (and it fact it had been reused successfully in a number of applications). Unfortunately, this attempt to reuse what was reused before failed, because the system didn't allow reusing this particular class without reusing others that were stored in the same library. Other classes in those libraries had their own dependencies, so whenever I tried to compile the application I would fail due to missing components. I tried to resolve the situation by adding those additional libraries and classes to the project, but as you can imagine, all those libraries had yet another set of dependencies, and so forth. In the end I would have had to reuse an entire application to reuse the class I really wanted when I started out. Still, I could have made this scenario work by reprogramming the desired component. I didn't do that, but I copied the class into a separate library to get rid of all the additional dependencies. That did the trick for a moment, but it didn't accomplish my ultimate goal, which was to reuse a data management component in the administrative tool of a larger application. It worked fine in the first version, but as soon as I changed the component in the main application, I had to go back to the administrator tool and make those changes there as well. In the long term, I didn't even achieve half of what I originally intended.

Now let's assume we didn't fall into either one of those traps. This should open the door for successful reuse. But what actually is "successful reuse"? Whether or not you'll be able to reuse components successfully depends greatly on your expectations. If you build small components (such as the Fox Foundation Classes described in Chapter 4), you might be able to reuse a large percentage of your code. However, applications are also built of much larger classes, and reusing those is much more difficult. When you create a framework, you should be able to design it so you can reuse all of it quite easily, but it might be impossible to reuse only one of the components. The data manager object described above will be very hard to reuse in different frameworks unless they adjust to the way the manager object does things. Does that mean I wasn't successful in creating reusable objects? I don't think so. When I designed this framework, I never expected those classes to be reusable individually. The most important point is that I save myself about 40% development time by reusing the framework together.

So now that you've learned about the most common reuse traps, you can have a look at how to do things the right way.

Application-internal reuse

People usually envision reusing code in various applications. However, the first step is to reuse code within a single application. In the first sections of this chapter I introduced a couple of classes that are designed for application-internal reuse (data managers, for instance). I believe that application-internal reuse accounts for more saved time than any other kind of reuse.

Reuse within one application is much easier to achieve than cross-application reuse because you don't face organizational issues. The key is to design clean interfaces and to abstract behavior properly so other classes can easily inherit from the class you want to reuse. The data manager classes at the beginning of this chapter are a good example of application-internal reuse.

 

Cross-application reuse

Cross-application reuse is the ultimate level of reuse. You create components once and reuse them in all your applications from then on. On top of the time you save, you get the advantage of updating all your applications at once or fixing bugs globally.

The easiest form of cross-application reuse is COM components. You simply instantiate a specific component in a COM client application without worrying about anything but the component's name and interface. In fact, this form of cross-application reuse is no more difficult than application-internal reuse. Not only is this the easiest way to reuse code across applications, but it also is one of the few ways to reuse it across various development environments. Unfortunately, there are some limitations to COM components. Most importantly, there is no inheritance or subclassing.

Another incarnation of reusable COM components is ActiveX controls. As we know, Visual FoxPro can't create ActiveX controls but it can use them. In fact, it can use ActiveX controls better than most other tools especially because Visual FoxPro introduces inheritance, which is a truly unique feature for binary components.

The most powerful form of reuse is code reuse on the class level. This kind of reuse provides access to all of Visual FoxPro's object features, including inheritance, pseudo-subclassing and instance programming. Unfortunately, this is also the most difficult version of reuse because you have to face all the issues at once, including object interface design, implementation logistics and more. This means that besides designing your application architecture, you also have to spend a good amount of time on designing your implementation. I generally try to separate framework classes from individual classes so the framework is easy to update. I also try to keep my class libraries small. This reduces the number of classes I reuse unwillingly along with others. A good guideline is to put all classes that depend on each other in one class library. If you can't do that (which is often the case when dealing with complex classes), you should add asserts (the new ASSERT command) that inform the programmer about all the dependencies, so if he forgets something and an error arises, the class will explain what is needed.

What if components get updated?

The time you save building applications with reusable classes or components is essential, but the advantages of reuse don't stop there. In my opinion, one of the most important points about reuse is that you can update your classes and all your applications will be able to take advantage of them immediately. This applies to bug fixes as well as new features. Obviously, you have to be extremely careful not to change the interface of your objects otherwise you could break your applications severely in a matter of minutes.

This reminds me that I should discuss acceptable interface changes. When I say "Don't change the interface," I'm referring to the existing interface, because it usually isn't a problem to enhance an interface. This means that you can easily add new methods and new behavior. You can even add to existing methods. New parameters, for instance, aren't a problem as long as they are optional. The return value (or the produced result in general) can also change, as long as it doesn't change when the method is called in the original way. In other words: The result should change only when the passed parameters change. This ensures that other applications that use the class but aren't aware of the changes will see the same result as before.

It might make you nervous to know that changes in one application will change other applications that use the same code. This drives me crazy, but it's good to know there are tools to help with this scenario. Microsoft Visual SourceSafe is one of them. It allows you to share components and to pin down a certain version of a component for a specific project. This means that a particular project won't get the updated version of a class before you explicitly say so.

A scenario you should avoid is to copy classes into separate class libraries, even if you don't change the code. This might give you some short-term advantages, but you wouldn't be able to update your application easily.

If you do all your work in a single environment, you can leave shared class libraries in one directory for all your projects. However, this might not always be possible. In my case, for instance, doing consulting work for different companies, I have to keep copies of my framework (or other classes) in special directories (typically at the customer's site or on a secure Web server). This is not a problem as long as I don't touch those copies. I always have one directory and one directory only where I keep the version that I modify. I then deploy new versions to all projects that need updating at that point. The projects I'm not working on remain with the pinned-down version of those components until I make changes (I use SourceSafe in all my projects).

Reusing components that don't match your architecture

You are likely to face situations where you need to reuse components that aren't designed for reuse. In this case you can use a wrapper, which is a class that is designed to match your architecture. This class then talks to the component you want to reuse in a way the component understands. The wrapper essentially acts as a translator or adapter.

I've already described a wrapper in this chapter. One of the data manager objects I used in my example dealt with ADO data. ADO is object-based data that can be used as a COM component. However, when I started to create my framework, ADO wasn't yet invented. When the need arose to use ADO, I created the wrapper class that translated the ADO methods and properties into something my framework could use.

Wrappers can be implemented in a variety of ways. A wrapper can just be aware of the class it handles, or it can contain and fully control that object. Either way, all the wrapper does is receive messages and translate them into something the object understands. The messages are then passed on to the object you want to reuse. The result is then translated back into something your system can understand. Finally, the result is passed back to the system that doesn't even know it just talked to a component that speaks a different language.

In the ADO example, this would mean that the ExecuteQuery() method is translated into a plain Execute() method that the ADO connection object can understand. The resulting recordset is then converted into a regular Visual FoxPro cursor.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net