FRAME ‘N’ SAVE

 

 

BY

DAVID G. MOOTER

 

FACULTY ADVISOR

DR. JAMES D. KIPER

 

 

A study of programmatic frameworks for use in software development

 

 

 

 

 

Submitted to the Miami University Honors Program in partial fulfillment of the requirements for University Honors in the undergraduate program

 

May 2001

Oxford, Ohio

Abstract

A framework is an abstract collection of classes that address a specific problem domain and are utilized by the extension of those classes into concrete solutions. It is the goal of this thesis to distinguish what framework oriented programming (FOP) is, to show that it goes beyond object-oriented programming (OOP) as the next step in the evolution of programming paradigms, and to explain how FOP can save development time in many situations. In addition, it will present and analyze an original sample framework and use it to show a general idea on how to create a framework.

FRAME ‘N’ SAVE

 

 

BY

DAVID G. MOOTER

 

 

 

Approved by:

_______________________, Advisor

_______________________, Reader

_______________________, Reader

Accepted by:

_______________________, Director, University Honors Program

Acknowledgements

Special thanks go to Dr. James Kiper for being the advisor of this project, Dr. Fazli Can for his generous donation of useful books, and Mr. Douglas Williams for ideas, good sources for information, and for lending me the book that generated the idea to make this topic into my thesis.

Table of Contents

Evolution of Paradigms *

Describing Frameworks *

Costs and Benefits of FOP *

Designing Frameworks *

An Example Framework *

Conclusions *

Appendix A *

Appendix B *

Bibliography *

Evolution of Paradigms

Programmers are lazy creatures: they are always looking for ways to make their job easier. This is not a bad thing since a product that is easy to produce should turn out better than a similar product that is very difficult to produce. This effort to make things easier has led to well-defined methodologies that have evolved over the years.

 

Process-Oriented and Procedure Programming

In the early days of programming there was no structure: programmers just wrote code and kept writing and modifying until it did what it was supposed to do. Then along came process-oriented programming and its more refined sibling procedural programming. The idea behind these philosophies is that every program has a beginning, middle, and end (usually corresponding with "input, processing, output") or some other form of procedure; the developer writes code that models this flow.

A typical approach to designing programs with this mentality is to write down all the processes that need to be performed. Then it is determined when each process is needed, where they are called upon, etc. From this model springs forth the structure of the program.

There is a strong focus on algorithms and procedures to efficiently get from one step of the program to the next. The developer also has access to all the system’s resources, meaning the operating system and any libraries of functions that come with the compiler. In this case the developer is in charge of creating not only the structure but also all functionality that the system resources do not provide since the system resources are the sole tools available to use as a program’s basis. Although this certainly was a vast improvement over the "write and keep hacking away until it works" approach, there are many inherent problems with this approach:

 

Object-Oriented Programming

Object-oriented programming (OOP) addresses many of process-oriented programming’s problems. Instead of passing data to procedures that act upon them, OOP takes such procedures and makes them an actual part of the data structure itself. (Such data types will be referred to as classes.) This makes maintenance much easier since the operations made for a specific class are contained within that class instead of being spread out across several libraries.

A common way to create programs via OOP is to develop a problem statement, extract nouns and verbs from that statement, and create classes and their member functions to model these nouns and verbs. Another approach would be to model real world things with classes and represent the interactions between these things with the class’s member functions and member variables.

This approach results in many improvements over the process-oriented approach:

Although OOP is a significant improvement over the process-oriented approach, there is still one major problem that it does not address: "putting together the pieces". The developer is still in charge of creating the program flow, making data interact appropriately, etc. Framework-oriented programming, as we shall see, provides a solution to this problem.

 

Design Patterns: a Step in the Right Direction

In his book A Pattern Language: Towns, Buildings, Construction, Christopher Alexander said:

"Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem in such a way that you can use this solution a million times over, without ever doing it the same way twice."

Even though he was referring to architecture and city planning, his ideas embody the ideals of design patterns within programming and have been the biggest inspiration to those who have developed the principles behind design patterns in programming. The idea behind design patterns is that many problems in programming have the same abstract structure and therefore their solutions will have the same structure. By abstractly defining the solution to one problem, you can achieve a solution to many problems.

Design patterns are general ideas for a structure that becomes an "instance of the pattern" when it is placed into code. In other words, design patterns are not specific sets of classes and functions; they are nothing more than a structural idea. Patterns consist essentially of two parts: the problem and the solution. Sometimes they even have a name attributed to them. To help clarify, let’s examine a design pattern called State.

You are writing a shell for an operating system and need to some basic file management functionality. A class called File will encapsulate this functionality. This class has functions such as Delete() and Open(). The file also has various states regarding access: it could be marked as read only, it might be in use by another process that has denied read access to it, it may be marked for deletion (i.e., it’s been moved to the Trash like on a Macintosh system), or it could be clear of any access limitations.

Let’s now create an abstract class called FileState and make a FileState pointer a private member variable of File. This class has abstract member functions called HandleDelete() and HandleOpen(); these functions are called respectively by Delete() and Open(), and it is the only thing that these two File functions do. Classes that derive from FileState would override this function to do concrete things: the class LockedState would override HandleDelete() to prevent it from being deleted since the file is locked; the class NormalState (i.e., no access restrictions) would override HandleDelete() to actually delete the file since it is permissible to do so; the class InUseState() would override HandleOpen() to prevent it from being opened since another process has control of it. A diagram of this structure would look like this:

The problem in its abstract form is essentially the need to have a client object behave differently depending upon its state. The solution is for the client to use a state object that defines and encapsulates behavior for various states. This problem and solution can be applied to many situations. For example, a TCPSocket class will respond to member functions Open(), Close(), Bind(), and Listen() differently depending on the state of the socket (opened, closed, listening, uninitialized, etc.). A drawing area class in a graphics program will behave differently when the function OnMouseDrag() is called depending on whether its state is DrawLine, DrawAirbrush, etc. An abstract diagram of this design pattern is described with this diagram:

This design has many benefits. For one, it encapsulates all state specific behaviors into one object. This allows for new states to be defined by simply deriving new classes from the State class and assigning it to the client as its current state when appropriate. Also, it may be found that a derived state class might be applicable to more than one client. Instead of coding the state’s behavior into two classes, the programmer needs only assign an instance of the same derived state class to both clients.

 

In Walks Frameworks

A good definition of frameworks comes from Ralph E. Johnson and Brian Foote of the University of Illinois in their paper entitled "Designing Reusable Classes":

"A framework is a set of classes that embodies an abstract design for solutions of related problems."

For the purposes of this paper, the abstract design of the above definition will be specified in more detail. It is a code structure in which program flow control is predefined; code created by the developer is utilized by the framework itself instead of the programmer directly utilizing the framework. This is in contrast to traditional function and class libraries where their functionality only works when invoked by the programmer. In this instance, the framework libraries invoke the programmer’s code.

Mr. Foote goes further in his paper entitled "Designing to Facilitate Change with Object-Oriented Frameworks" and gives a good way to differentiate between a framework library and a traditional object-oriented class library:

"Where a class library contains a set of components that may be reused individually in arbitrary contexts, a framework contains a set of components that must be reused together to solve a specific instance of a certain kind of problem."

Another key to understanding why frameworks are not just class libraries is that a solution using a framework arises by extending the framework’s code; class libraries are simply instantiated and called, but not extended.

This is also a key for distinguishing frameworks from another similar construct: program skeletons. A skeleton is a set of code outlining the general behavior for a type of program. The difference is that a skeleton is typically utilized by modifying and inserting into the skeleton’s code directly instead of extending its code. Conversely, a framework is not necessarily a skeleton. A skeleton must define general behavior for a program. A framework on the other hand does not have to define any specific behavior, but rather define only abstract behaviors thus allowing the programmer to define all real specific behaviors. Note, though, frameworks that are purely abstract are rare. Nonetheless, a framework can in fact be a specific type of skeleton.

The idea that a framework can embody an abstract design for solutions of related problems derives from the idea that many problems within a domain will have the same core structure, flow control, etc. This idea goes from low level specifics with things such as scroll bars (all scroll bars have like behavior) on up to the high level structure of a program (all GUI applications behave by waiting for user generated events and responding to them).

This may sound familiar; it is very similar to the ideas of design patterns. In fact, the two go together very well. Building a framework with design patterns as its foundations can make for good framework design: pieces of the frameworks are easier to describe, and it opens up a new level of design reuse where implementations vary but the code’s micro-architectures are identical. In fact some go so far as to define frameworks as a collection of implemented patterns; the article "Modeling Components and Frameworks with UML" published Oct. 2000 on pp. 31-38 of Communications of the ACM describes frameworks as a "stereotyped package consisting mostly of patterns" or an "architectural pattern that provides an extensible template for applications within a specific domain".

Frameworks, though, go further than just design patterns. Even though they are based on similar principles there are a few differences between them.

 

Design Patterns vs. Frameworks

The clearest difference is that frameworks are made of actual code while design patterns are ideas for code structure. Since both things come from the same types of ideas, are frameworks nothing more than implementations of design patterns? In some cases this is true, but frameworks can be much more complicated than the typical design pattern; often times they include implementations of several different patterns. Also, the thrust of design patterns is to reuse structure while frameworks focus on reusing both structure and actual written code. In other words, design patterns are simpler and more abstract than frameworks.

Another difference is that frameworks target specific domains. Design patterns are clearly applicable to many unrelated situations; that is one of their most redeeming features. Frameworks have a specific thing in mind; they are designed to be a "graphic design framework" or a "document/view GUI application framework", etc. Design patterns try to solve a design problem; frameworks try to solve a domain problem. This does not mean that a framework solves fewer problems than a design pattern. A framework such as the ActiveX framework in Visual Basic (which is essentially the language itself) certainly solves many more problems than a typical design pattern. This framework targets a very generic domain: Win32 applications. But the key here is that Win32 is a definite domain despite being extraordinarily broad and diverse. In addition notice that Visual Basic has to be enormously complex to solve such a broad spectrum of problems while a pattern typically solves numerous problems while being very simple.

 

Frameworks: the Next Step

It was stated in the introduction that programming in frameworks is not just fancy OOP, but rather it is the next species in the evolution of programming paradigms. There is one key aspect of FOP that distinguishes it from OOP and takes the step forward that design patterns can’t do alone: it reuses flow.

Creating the structural flow of a program has always been a big factor. Sometimes it is the most complicated and most difficult piece to create. When programming using FOP, the structural flow has already been created for the programmer, something that no other programming methodology has been able to do.

Exactly what makes this such a big change? It requires a change in the way programmers think. The shift to process-oriented programming required programmers to stop writing random spaghetti code and start breaking programs into modular procedures and subroutines. The transition from process-oriented and procedural programming to OOP required programmers to stop thinking in terms of passing data to procedures and start thinking in terms of putting procedures in the data itself. They also had to think in terms of polymorphism and data/function encapsulation. The shift to FOP now requires programmers to stop thinking of calling library functions and tool kit classes; instead, the libraries and tool kit classes call the programmer’s code. The programmer no longer runs the show because the framework libraries take charge.

The developer must concentrate on class models. It is vital to make a good analysis of what types of entities to create, their interface, the inheritance structure (for if inheritance is available), and how entities interact. The programmer still must decide what the entities are required to do, but the framework is now the one that decides when these actions are performed as will be shown in the next sections.

Describing Frameworks

Frameworks are composed of a group of classes stored in various libraries that the programmer includes in projects. This sounds just like normal class libraries like the ios library from the C++ language (in the file iostream.h) but there’s more to framework libraries than just that; there are some features that they include which are not seen in common class libraries:

Standard OOP class libraries on the other hand often have few interactions defined, are not easy to customize via sub-classing, are instantiated and called by the programmer, and contribute little to a program’s structural flow.

 

Common Structures of Frameworks

Most, and possibly all, frameworks have a commanding class that runs the entire system. A typical way to start a framework is to instantiate this commander class, set its initial values and state, and then call an initiation member function. This function would then create the necessary objects and call the appropriate functions to carry out the desired task. Typically the class never returns from this function call until the program has terminated and therefore the programmer will add no additional code after calling this function.

A typical structure for frameworks designed to build general applications is for the commanding object to check for events and pass these events to the rest of system, essentially an event driven system. In fact, it is difficult to imagine a framework that is not event driven due to the way the framework calls the programmer’s code so it may be safe to say that all frameworks are designed this way. These events can come from the OS (like a signal for the program to quit), the user (such as a mouse click), or the program itself. When a member of the framework sees an event that it should respond to, such as a check box that sees a mouse click event in its area of the screen, then that object will respond with appropriate behavior.

There are two techniques for implementing the system: derivation and composition. Both have strengths and weaknesses.

Composition is a way of building a new class out of others by assigning references to other classes within the new class. This allows the behavior of such a class to change dynamically at run-time by modifying the things that compose the object. Classes based on the client of the State pattern described earlier would be a very good example of composition. The disadvantage of composition without derivation is that new classes cannot be used as substitutes of others since they do not share the same type.

Inheritance makes it easy to use many different classes in the same context. A sub-class’ member functions easily override its parent class’ behavior at compile time. The down side is inheritance is more static than composition. Not only are classes fixed at compile time, but also their design is bound by their parent class’ structure.

Composition tends to make for more flexible designs than pure derivation alone. As stated above, it is mutable at run-time, but also the implementation of the client classes in the composition tend to be simpler. The fact is, though, that composition often depends on derivation. The State design pattern, for example, requires derivation to produce the various states that compose the client class. Also, there are times when you want to limit a class’ behavior, and object composition makes things too free to do so since the components’ behavior is not always controllable by the class they compose. The best structure would be to build with a good mix of both composition and derivation exploiting the benefits of both.

 

Target Domains

As mentioned before, frameworks target a specific problem domain. These domains can be described on a very high level such as a general application framework or a system framework (one that is used to develop hardware drivers, for example), or they can be described very specifically such as a framework for building 3D games or an Internet application framework. Frameworks that target the domain of general applications are the best known. Apple’s MacApp, PowerPlant by Metrowerks, Microsoft’s MFC, and Sun’s Java libraries are four such systems. Such frameworks are the most useful since they can be applied to seemingly innumerable types of applications.

Costs and Benefits of FOP

Clearly the number one goal of a programmer is to complete projects on time and on budget, and reuse of code is likely the most popular way to do this. Libraries used in other forms of programming such as OOP do offer some reuse, but such reuse is at a low level. Unlike standard libraries, libraries that make up a framework define the interconnections between themselves. This increases overall code reuse drastically. There are other benefits of frameworks besides this.

These are tremendous benefits, but there are costs too. For one, designing a framework to begin with is very difficult and time consuming. Some have called it the most difficult programming endeavor. For many people it is also difficult to program within a framework. This is likely due to the change of mindset from OOP to FOP just as the shift from process-oriented to object-oriented was difficult for many. In addition, programs are constantly jumping between the programmer’s code and the framework’s code which can make debugging more difficult. Finally, even for those who find frameworks easy there is still a steeper initial learning curve to learn a specific framework than traditional programming since one must learn many more classes.

These costs seem to be outweighed by the benefits when utilized correctly. This is possibly evidenced by the fact frameworks are being used the most popular languages today, many of which have a framework built into the language. It may be even more evidenced by Visual Basic, which is built as an ActiveX framework and is renowned for its rapid development time. The difficulty and extended time needed to create an initial framework may be paid off in the long run by the time it saves in program development. Even if there is neither the time nor resources to do such a task, there are commercial frameworks for sale making the need to create your own irrelevant when an appropriate product can be found. The difficulties that programmers have with FOP are no different than what process-oriented programmers go through when they discover OOP. It just requires getting used to, and once FOP is mastered the payoffs are realized.

Frameworks can have a very different impact on a project’s cost and development time depending on the size of the project being developed and whether or not a prefabricated framework is being used (as opposed to developing the brand new framework from scratch specifically for the project).

 

Small Projects or Short Life Span

A small project or one with an expected short life span often needs to be "pushed out the door" as quickly as possible. Since developing a framework is a very time consuming task it is obviously not a good idea to design a framework specifically for one such project.

On the other hand, it may or may not be wise to use a prefabricated framework for such an endeavor. Although frameworks are sometimes difficult to master, they are not difficult to use once they are mastered. Plus some frameworks are easy to use and easy to learn. The best example of this would be Visual Basic, a language based on an almost object-oriented, ActiveX framework; it is easy to learn and is often the most ideal tool for small projects. In the case of a framework that requires very little training (such as Visual Basic) or one that has already been mastered by the developers, using a framework can often times increase developmental productivity.

 

Larger Scale Projects

For larger projects, a framework can often make things easier to manage. Managing execution flow control is often a difficult part of creating large, complicated programs. Since execution flow control is exactly what frameworks encapsulate, using frameworks often make development and maintenance much easier. This is especially true of event-oriented software such as GUI based programs (spreadsheets, graphic editors, and so on). In such cases it may be true that creating a new framework from scratch would be beneficial, though of course using a prefabricated one from a vendor is the fastest to get started if such a product is available.

 

Repeated Projects

Frameworks address the solution to a specific problem domain. Therefore, the benefits of framework code reuse are most noticeable when developing several programs within the same problem domain. Even though creating a new framework can be costly, the savings in using a framework for such projects well suited for the problem domain can easily outweigh the costs of developing such a framework, even if many of them are not large projects.

Designing Frameworks

Even though there are retail frameworks, there may be times when a software developer will want to create a new framework that better suits his programming needs. It is vital that the framework is designed correctly from the beginning: creating frameworks is a very time consuming process so it would be quite costly to go back and fix errors. Just as in any form of software development, the most important design decisions are formed during the analysis of the system. There are three primary aspects that must be addressed in design: domain, description of objects and their relationships, and finally the framework’s behavior.

 

Domain

The first and most important factor in designing a framework is the walk on the fine line that divides generality and functionality. Obviously it is important to have lots of useful functionality. After all, the benefits of code reuse are moot if the reused code has little useful functionality. On the other hand, adding functionality makes the framework less generic and more specialized. If it becomes too specialized then the contexts in which it can be used will be limited, and therefore it will become less reusable.

To approach this problem a framework designer must begin by deciding on the problem domain the new framework will address. It could be general enough to address all applications written for a specific platform, or it could be specialized for a specific type of application, such as 2D graphic editors. This does not address the problem entirely, though. Since a problem domain will probably have many sub-domains the framework must stay general enough to service all the sub-domains. For example, a 2D graphic editing framework needs to service paint, draw, photo editing and touch up, etc. If the framework becomes to specific to photo editing applications it may become unusable for a draw program. One solution to this is to add functionality that addresses each sub-domain specifically but can be easily ignored by programmers using the framework if so desired. MFC, which targets all Windows applications in general, takes this approach. There is support for multiple document applications, but it is optional; there is support for Internet applications, but it is optional; there is support for many different types of document views and standard file management techniques, but they are all also optional. The advantage of this approach is that it addresses a problem domain to a much finer detail than a more general framework without sacrificing any sub-domains. On the other hand, the framework’s complexity seems to increase exponentially.

 

Entities and Relationships

The next aspect in designing a framework is to describe its entities and their relationships. One good method is to go back to the idea of design patterns and use them as a language. Find and identify patterns that occur in the framework, and then use the names of the patterns to describe the framework. From this description will spring forth flow charts, structure charts, component hierarchies and relationships.

Consider a framework being designed for the problem domain of all applications in general like PowerPlant or MFC. There would probably be a component representing the application itself. Since it makes no sense to have more than one instance of this component then it should be a Singleton (this and all the following pattern names come from Design Patterns: Elements of Reusable Object-Oriented Software published by Addison-Wesley; please refer to appendix A for diagrams of these specific patterns. When events such as a keystroke occur it will be necessary to inform elements of the framework of the event and provide a way for the appropriate element to respond. Therefore the framework is a Chain of Responsibility or some variant of it. Controls in a window are often times created via object composition using other controls; for example, a combo box in Windows is composed of a menu, scroll bar, and text labels. All controls, even those composed of others, will also all need to derive from a common class so that their controllers in the Chain of Responsibility can communicate with them. This is Composite. Some entities may need to behave differently depending on their state, such as a menu in a menu bar or as a pop-up menu in the middle of a window, so there will is a State pattern. To address the differences between sub-domains of the target problem domain it may be necessary to produce many subclasses of core elements that are used independently. A view class like CView and its children in MFC is one such example. To produce these varied subclasses through the same interface the framework will also be a Factory Method or a similar cousin pattern (like Abstract Factory). This can go on and on until the entire framework has been described to the satisfaction of the developer. Since design patterns describe things such structure, flow, and component inter-relationships, using the words Singleton, Chain of Responsibility, Composite, and Factory Method provide a distinct description of the framework and its components. Obviously some patterns will arise which have not been defined before and have no names; in this case give it a new name and document this new pattern. Finally, not every part of a framework can be described with patterns. This technique just provides a very good start as well as a very good way to describe key components of a framework; the remaining parts of the framework should be designed through traditional analysis techniques.

 

Defining Behavior

The final key to developing a framework is defining behavior. Quite simply, a framework should provide default behavior when appropriate but allow for that behavior to be overridden. A class for a window in a GUI should close by default in response to a click in its close box, but it should allow the programmer to override this for cases when the window does not get closed such as a user choosing cancel when asked to save a window’s document. Not only is it plain common sense to provide default behavior, but the way a framework’s default behavior acts also represents a model to the programmer that serves as an example of how the framework is supposed to behave. If done well, this improves understanding of what specific classes are supposed to do. In addition, the method provided to the programmer for overriding such default behavior must be easy to implement.

An Example Framework

The example object-oriented framework presented here targets the domain of TCP/IP file servers in general using C++. Suppose you wanted a framework to abstract servers. There are two things that could be advantageous to this. Once the framework is in place it should be relatively easy to produce new service programs using the framework as your starting point. On the other hand, once a specific service is programmed, it could also be easy to port it between many operating system platforms by porting the framework to different platforms. The following example focuses on ease in producing new services. When it is complete, it will be shown how to extend the framework to allow for ease in porting to different operating system platforms.

The Objects

After analyzing what servers generally have and how they behave, a possible set of classes such as the following might arise. Obviously there will need to be a class for sockets. In particular, it will need to handle connected sockets and daemon sockets, or maybe it would be better to have a different class for each of these two cases. In this example, there will be two different socket classes, and the socket classes will be made to do TCP/IP sockets only since TCP/IP has nearly monopolized transport protocols. That takes care of the transport layer of the ISO. A class for the file system will be necessary. By abstracting the file system with a class the specifics of the operating system go away making portability easier. In addition, it allows for "virtual file systems" to be constructed such as for e-mail in which your in-box acts as the file system. When actually accessing files, a class to abstract files would be useful. Since this framework is in C++ and that language provides classes for file I/O, it is not necessary to create a new one; this example will simply exploit the capabilities of the ifstream and ofstream classes. All services require some sort of user authentication. Therefore, a class that authenticates who the client user is will be needed. This class will also be where the system goes after authentication to ask who the current user is. Finally, every service has its own protocol, and thus there will be a class that sits over everything taking its input from the socket class, interpreting what it means, and carrying out the appropriate actions; this is essentially the application layer of the ISO.

Finally, like all frameworks, this is going to be event oriented. When a new connection comes in, an event needs to fire off the listening socket to get things initiated to handle the new connection. The protocol class needs to fire periodically to check if anything has come through the client’s socket or if it needs to send anything through to the client via its opened socket object. This can be performed with threads or something similar, but that is of course operating system specific. Therefore, in the interest of portability and higher abstraction, an event manager should be placed into the system. This can be performed with two classes: an event manager (or event kernel) and an event client. The event client will be a purely abstract class from which all classes that need event polling capabilities shall derive. The event manager will have a list of all instances of these clients. It will loop through each client and call the interface provided in the event client class for polling events. This particular part of the system may be more difficult than the rest to picture, so for clarification here is digression with an example of how this could be constructed.

 

The Event Processing System

Let the main event manager class be called EventKernel. It will follow the Singleton design pattern as described in Design Patterns: Elements of Reusable Object-Oriented Softwarewith getInstance() as its public instantiating method. It will store a list of objects of the type EventClient. It will have at least three public methods: bool addHandler(EventClient *addMe), bool removeHandler(EventClient *removeMe), and int doEvents(). The first two methods are used to add and remove EventClient objects to and from the internal list of EventClient objects (their bool return values could indicate whether everything went OK or an error caused the action to fail). Before going to the third method, it is necessary to examine the workings of EventClient.

The header for EventClient would be as follows:

class EventClient

{

public:

EventClient();

virtual ~EventClient();

virtual void doEvents()=NULL;

};

The constructor would have one line in it: "EventKernel::getInstance()->addHandler(this);". The destructor would be similar: "EventKernel::getInstance()->removeHandler(this);". Therefore, any time a class that derives from this pure virtual class is instantiated, it is automatically added to the list in EventKernel, and it is automatically removed when it is deallocated. The purely virtual method doEvents() is defined by derived classes to do whatever event polling and handling of polled events that the class needs to perform. And this brings us back to int doEvents() method from EventKernel. It simply infinitely iterates through every item in the current list of EventClients and calls their doEvents() method. In fact, once it is called (presumably by the main() function), it might never return. If you were to add the capability to signal a quit event and let the EventKernel class know that it is time to stop, then its int return value could be the exit code for the actual program; but in this example it will be assumed that the server runs forever. With this system for event handling, the two classes in the server framework that need event polling can derive from EventClient: the daemon socket and the protocol class.

 

Class Relationships

It is now necessary to determine the relationships between the classes. After analysis, here is an example of what may come up. Clearly things greatly center on the protocol class; so it will be the owner of most things. It will be have a file system object and an authenticator object since it will be using the functionality of these two to perform certain required tasks. Note that the file system class may also need access to an authenticator object. For example, different users may be given a different root directory like the program Hotline, or they may be given different "virtual file systems" as is the case with e-mail where the in-box presented to the client depends on which account is logged into the server. So thus the file system will have an authenticator. And in fact it obviously would have to be the same instance as the authenticator in the protocol object to which the file system object belongs. The protocol class will have an open socket class to for the actual communications with the client. Finally, the daemon socket will maintain a list of all the protocol objects it has instantiated and will be responsible for deallocating them when their session with the client ends.

 

Class Public Interfaces

Now that classes are identified and relationships are established, it is necessary to determine the public interfaces to these classes. Bear in mind that all these classes (excepting the EventKernel class and two socket classes) will be virtual classes; derived classes will be used to define their behavior, and then the framework will determine when to call these methods.

A simple class is the authenticator class. It will have a method that accepts a string with authentication information such as user name and password and will return true or false depending on if it succeeded. It would also need an accessor method that returns the user ID of whomever is logged in, an empty string indicating no one has been authenticated yet.

The file system class will need to provide access to files and directories. Listing directories is necessary, so it will have a method that returns a list of strings containing the file names for the current directory. It would also need a method that tells it to change directories. The ability to read file attributes would be necessary. A method that accepts a string and returns a string could work for this; derived classes would then have the actual definition of what the attributes it understands are and how they are returned in the return string. Finally, a method that opens files and returns its corresponding fstream object would be needed for initiating file I/O. One additional note is that its public constructor must accept an authenticator object since that needs to be specified by its owner protocol object so that it matches the authenticator object that the protocol object uses.

The protocol class will not have the doEvents() method required by its parent class, EventClient; that will be left to concrete subclasses to define. The daemon socket needs to know if the connected socket being used by the protocol object has been closed, so thus the protocol class will have an accessor function that says whether or not its socket is still connected. As a side note, its constructor will need to accept an opened socket object so that when a daemon socket spawns a new connection, it can create the protocol class with the socket that is connected to the client.

The opened socket class will have an interface to the standard socket functions: open, close, send, read, etc. It will also need a status interface so that the protocol object can see whether the socket has been closed due to a disconnection by the client socket. The daemon socket class need only define the doEvents() method of its parent; all other details may be handled internally by the event management within doEvents(). Review the section on the event handling system for the public interfaces of the objects involved in those classes.

Once this is all complete you will have a model that looks something like this:

* Note that in the above diagram the return type for listDir() should be List<String> and openInput()and openInput() return respectively ofstream and ifstream. The software that generated this diagram seems incapable of specifying these data types.

At this point all three aspects of framework design have been addressed. Domain was specified in the beginning: TCP/IP file servers in general. All the entities (classes) have been described with their relationships. All virtual methods happen to be pure virtual in this case and so no default behavior actually exists in this system.

 

Results and Possible Improvements

Having designed, purchased, or downloaded the code for this system, it is now time to code an actual server with this system. Suppose that you desire to write a file transfer server for Linux. To begin, a concrete file system and authenticator class will need to be defined. Linux has its own built in user authentication API. Thus you could create a concrete subclass of Authenticator called AuthenticatorLinux that abstracts this API. Then file system would be a simple matter of moving through the Linux root file system; this concrete subclass of file system might be called FilesystemLinux. Finally, a concrete subclass of Protocol would be written. It would define the doEvents() function to take input from its connected() socket and respond according to its set of rules for client input.

Next you wish to create a simple mail server. Again, you need to begin with a concrete authenticator and file system class. Fortunately you already have an authenticator class that will suit your needs here: AuthenticatorLinux. Reusing this from the previous server means that by writing the file transfer server then you also wrote a third of the mail server. This saves tremendous time and makes code much easier to understand. Thus all you need to do is write a file system class abstracts a user’s in-box instead of the Linux root file system and, of course, a protocol class that understands POP3.

Finally, your server’s network now has an LDAP authentication system installed and your two servers need to be able to authenticate with this in addition to the normal Linux method, but the sysadmin does not want LDAP configured into the Linux authentication API. Thus it is up to you to manually support it within your own programs. Again, create a concrete sub-class of Authenticator called AuthenticatorLDAP that abstracts the LDAP authentication process. But this will not suffice: we need to support the old method, too. Thus a trivial AuthenticatorUniversal class can be written that internally instantiates an authenticator for LDAP and Linux API and then uses one followed by the other for authentication following the principle of object composition. Then down the road if you are working on a project that only requires LDAP authentication, the AuthenticatorLDAP class could be used by itself with no additional coding. This is again a clear example of the significant code reuse through modularity and abstraction, and such code reuse always saves time. In addition, all the details of program structure especially in regards to flow of execution control are defined entirely by the framework and do not need to be addressed by you during development. This again makes development easier and swifter.

It is clear that the code reuse here occurs between different server applications. It does not address the issue of porting between differing operating systems. The protocol class and classes involved in event management are clearly not platform dependent. The socket classes most definitely are. Depending on what sort of file system you are accessing and how you authenticate users, the file system and authenticator classes may or may not be platform dependent. Abstracting support for different operating system platforms may be a desirable improvement. This can be performed by sub-classing, which would require turning the socket classes into abstract classes. This can be messy for the authenticator and file system classes since you are already sub-classing to abstract for other purposes. Another, probably more desirable method, would be to use the preprocessor to determine the platform. Code for all platforms in the same classes but use the preprocessor to determine which code to compile. This is not at all uncommon in cross-platform development. And in fact if this were a package (meaning that the base classes were acquired from another source and you are simply utilizing them for your own needs) then such capabilities would be in place for the socket classes provided the package’s author intended for it to be cross-platform. In such a case the author might also include commonly used cross-platform versions of the file system and authenticator classes. Thus your responsibility in making it cross-platform would be minimal. And in fact if commonly used cross-platform versions of the file system and authenticator classes were included then your job may be nothing more than creating concrete protocol classes!

Conclusions

A definition of frameworks has been presented here. They are a set of classes based on an abstract design for solutions of related problems, and that abstract design defines program control flow by calling and instantiating code written by the application developer to run atop the framework. In addition, using frameworks effectively requires a change in the programmer’s mindset from traditional programming methods such as object-oriented programming due to the fact that frameworks take away control of things from the programmer.

Frameworks have been compared and contrasted to design patterns. Both embody an abstract design to solve common problems. Frameworks are actual code that addresses a specific domain, but design patterns are uncoded structural ideas that do not address a specific domain. Frameworks also can be quite complicated while design patterns strive for simplicity.

A description of frameworks and their design has been presented. They typically harness the power of object composition and derivation and can be described by describing the relationships between objects via composition and derivations. Their design goes in tandem with this: one must address the entities and their relationships as well as their behavior and the problem domain to be targeted when designing a framework. An example of this has been presented with a simple demonstrative framework designed by the author for Internet server applications.

Finally, frameworks when used effectively can improve development time and maintenance. Effective use of frameworks increases the amount of code reused. A framework also enforces similar structures on programs developed with it thus making it easier to understand one after another one made with the same framework is understood.

Appendix A

Abstract Factory

Chain of Responsibility

Composite

Factory Method

Singleton

State

Appendix B

In addition to this research, the author has written his own prototype framework for general Macintosh GUI programming. The code is far too enormous and complex to be presented in full here, but a main() function that utilizes the framework and some screen shots of what this primitive application does are presented in this appendix. Note that this code has been placed into Word version 8 for Macintosh (Word 98) and may not be displayed with the exact spacing as intended if viewed in Windows or a newer version of Word.

 

The main() Function

void main()

{

//The CommandManager manages events from the operating

//system and the application itself. It is a Singleton

//pattern. It is in charge of receiving events and

//sending them to the application. Any event handling

//object that decided to handle the event then returns

//code to be executed for the actual handling of the

//event. The CommandManager also is in charge of seeing

//to it that such event handling code is executed.

CommandManager *commander=CommandManager::getInstance();

//Now we create holding variables to store objects

//between when they are initialized and when they are

//inserted into the application

MenuBar *tempMenuBar; //The menu bar in the

//application

Menu *tempMenu; //A menu in the menu bar

MenuItem *tempMenuItem; //An item in a menu

AppleMenu *tempAppleMenu; //The Apple menu in the menu

//bar

//(a special type of menu)

SubMenu *tempSubMenu; //A submenu in a menu

//(a special type of menu)

Window *tempWindow; //A window in the application

Cntrl *tempControl; //A control (widget) in a

//window

RadioGroup *tempRadioGroup; //A group of radio buttons

//(a special type of control)

AppManager *myApp=new AppManager; //The application

//in the command

//manager

//Connect the app to the command manager

commander->initializeApp(myApp);

//Windows are created with a name, in this case "Window

//2". All windows in the application have a unique name

//for identifying them. It is also created with a

//titlebar caption, its top-left coordinates, and it

//width and height. After initializing the window,

//attempt to insert it into the application.

tempWindow=new Window("Window 2", "This Is a Window 2!",

254, 153, 427, 204);

if(!myApp ->addWindow(tempWindow)) return;

tempWindow=new Window("Window 1", "This is Window 1...",

59, 53, 396, 232);

if(!myApp->addWindow(tempWindow)) return;

//Next we create concrete controls. All of them derive

//from the abstract class Cntrl. So we cast them as

//(Cntrl *) to be able to store them in tempControl.

//Each control is assigned to a window. Each control

//has a name that is unique among other controls within

//its window. In addition, many control are also made

//with a caption, a top-left coordinate, and (if the

//control does not automatically size itself) a width

//and height. Note that all of the objects here being

//cast to (Cntrl *) such as those of type PushButton

//ultimately derive from the abstract class Cntrl.

tempControl=(Cntrl *)new PushButton("Push Button 1",

"Beep!", 5, 5);

//After creating the control, we must define its

//behavior. The event evPushButtonFire in the event

//group "PushButton" is generated by the push button

//when the user clicks on a push button and raises the

//mouse button while the cursor is still on the button.

//So the first value in this call says which event this

//is a handler for; the button will be able to tell

//automatically when such an event is for itself and

//not another button. All controls have a value. For

//push buttons, it is always "" since they are

//stateless (unlike, for example, a scrollbar whose

//value is where the scroll box’s position is the

//value). When specifying how to handle an event, we

//specify also what value the control must have. The

//value "" is means to do this when it handles the

//event and the control’s value does not match a value

//in the event handler look-up table. That is the

//second value in this function call. Finally, cmdBeep

//is a function that is defined in the framework’s

//library to call the alert chime API. So in

//other words, this button will make the computer

//beep when it is clicked.

tempControl->addCommand(EventTypeRec(evPushButtonFire,

"PushButton"), "", cmdBeep);

((PushButton *)tempControl)->setHotkey('\r');

//Now that the control is initialized, we add it to a

//window.

if(!tempWindow->addControl(tempControl)) return;

tempControl=(Cntrl *)new Checkbox("Checkbox 1", "1",

50, 50);

//Note that checkboxes have some predefined event

//handling, something that push buttons did not have.

//To tell the control to initialize the event handler

//look-up table to these default event handlers we do

//the following call to the abstract function

//initializeDefaultCmds. If we wanted to add to this

//then after this call we would use addCommand as was

//done with the push botton above. But in this case we

//will leave it alone.

tempControl->initializeDefaultCmds();

if(!tempWindow->addControl(tempControl)) return;

//We want some radio button. They do not work alone.

//Instead, they go into a concrete control called

//"RadioGroup". So we must make this container object

//first and stick it in the window.

tempRadioGroup=new RadioGroup("Radio Group 1");

if(!tempWindow->addControl(tempRadioGroup)) return;

//Next we create radio buttons and insert them into

//the radio group object.

tempControl=(Cntrl *)new Radio("Radio 1", "A",

5, 100);

if(!tempRadioGroup->addRadio((Radio *)tempControl))

return;

tempControl=(Cntrl *)new Radio("Radio 2", "B", 5,

120);

if(!tempRadioGroup->addRadio((Radio *)tempControl))

return;

//A simple text label…

tempControl=(Cntrl *)new Label("Label 1",

"Hey! I'm a label!", 150, 0, 56, 3);

if(!tempWindow->addControl(tempControl)) return;

((Label *)tempControl)->setStyle(italic);

//A simple scrollbar with horizontal allignment.

tempControl=(Cntrl *)new ScrollBar("ScrollBar 1",

sboHorizontal, 100, 50, 200);

if(!tempWindow->addControl(tempControl)) return;

((ScrollBar *)tempControl)->setMinValue(1);

((ScrollBar *)tempControl)->setMaxValue(100);

((ScrollBar *)tempControl)->setValue("1");

((ScrollBar *)tempControl)->setPageSize(40);

((ScrollBar *)tempControl)->setUnitSize(10);

((ScrollBar *)tempControl)->setStartTickDelay(30);

((ScrollBar *)tempControl)->setTickDelay(5);

//Next we make the menu bar and stick it into the

//application

tempMenuBar=new MenuBar;

myApp->initializeMenuBar(tempMenuBar);

tempAppleMenu=new AppleMenu;

tempMenuBar->initializeAppleMenu(tempAppleMenu);

//The Apple menu will have the traditional "About"

//item. Let’s make it just call cmdBeep three times

//in a row when it is selected. Notice that no events

//or values are specified here like for controls.

//This is because menu items never have a value and

//menu items only respond to being selected by the

//mouse and no other events.

tempMenuItem=new MenuItem("About Item",

"About This Boring App…");

if(!tempMenuItem->addCommand(cmdBeep)) return;

if(!tempMenuItem->addCommand(cmdBeep)) return;

if(!tempMenuItem->addCommand(cmdBeep)) return;

if(!tempAppleMenu->addMenuItem(tempMenuItem, 0))

return;

tempMenu=new Menu("File Menu", "File");

if(!tempMenuBar->addMenu(tempMenu, 0)) return;

tempMenuItem=new MenuItem("New Item", "New");

if(!tempMenu->addMenuItem(tempMenuItem, 0)) return;

if(!tempMenuItem->setHotKey('N')) return;

tempMenuItem=new MenuItem("Open Item", "Open...");

if(!tempMenu->addMenuItem(tempMenuItem, 1)) return;

if(!tempMenuItem->setHotKey('O')) return;

//This next menu item will get a submenu assigned to

//it...

tempMenuItem=new MenuItem("Open Recent Item",

"Open Recent");

if(!tempMenu->addMenuItem(tempMenuItem, 2)) return;

tempSubMenu=new SubMenu();

tempMenuItem->setSubMenu(tempSubMenu);

tempMenuItem=new MenuItem("Bob Item", "Bob");

if(!tempSubMenu->addMenuItem(tempMenuItem, 0))

return;

tempMenuItem=new MenuItem("Hey Item", "Hey");

if(!tempSubMenu->addMenuItem(tempMenuItem, 1))

return;

tempMenuItem=new MenuItem("Yo Item", "Yo");

if(!tempSubMenu->addMenuItem(tempMenuItem, 2))

return;

tempMenuItem=new MenuItem("Close Item", "Close");

if(!tempMenu->addMenuItem(tempMenuItem, 3)) return;

if(!tempMenuItem->addCommand(cmdCloseFrontWindow))

return;

if(!tempMenuItem->setHotKey('W')) return;

tempMenuItem=new MenuItem("Save Item", "Save");

if(!tempMenu->addMenuItem(tempMenuItem, 4)) return;

if(!tempMenuItem->setHotKey('S')) return;

tempMenuItem=new MenuItem("Save As... Item",

"Save As...");

if(!tempMenu->addMenuItem(tempMenuItem, 5)) return;

//cmdCloseFrontWindow is a function defined in the

//framework’s libraries to find the front window

//and tell the window to go through its close

//routine.

if(!tempMenuItem->addCommand(cmdCloseFrontWindow))

return;

tempMenuItem=new MenuItem("Divider 2", "-");

if(!tempMenu->addMenuItem(tempMenuItem, 6)) return;

tempMenuItem=new MenuItem("Beep Item", "Beep");

if(!tempMenu->addMenuItem(tempMenuItem, 7)) return;

if(!tempMenuItem->addCommand(cmdBeep)) return;

tempMenuItem=new MenuItem("Divider 3", "-");

if(!tempMenu->addMenuItem(tempMenuItem, 8)) return;

tempMenuItem=new MenuItem("Quit Item", "Quit");

if(!tempMenu->addMenuItem(tempMenuItem, 9)) return;

//cmdQuit is a function defined in the

//framework’s libraries to inform the command manager

//object to terminate the program.

if(!tempMenuItem->addCommand(cmdQuit)) return;

if(!tempMenuItem->setHotKey('Q')) return;

tempMenu=new Menu("Edit Menu", "Edit");

if(!tempMenuBar->addMenu(tempMenu, 1)) return;

tempMenuItem=new MenuItem("Undo Item", "Undo");

if(!tempMenu->addMenuItem(tempMenuItem, 0)) return;

if(!tempMenuItem->setHotKey('Z')) return;

//This menu item will start out as "inactive" (grayed

//out and unselectable by the mouse).

tempMenuItem->setActive(false);

//Menus with their caption set to "-" are special;

//they are made into menu divider bars and behave

//accordingly.

tempMenuItem=new MenuItem("Divider 1", "-");

if(!tempMenu->addMenuItem(tempMenuItem, 1)) return;

tempMenuItem=new MenuItem("Cut Item", "Cut");

if(!tempMenu->addMenuItem(tempMenuItem, 2)) return;

if(!tempMenuItem->setHotKey('X')) return;

tempMenuItem->setActive(false);

tempMenuItem=new MenuItem("Copy Item", "Copy");

if(!tempMenu->addMenuItem(tempMenuItem, 3)) return;

if(!tempMenuItem->setHotKey('C')) return;

tempMenuItem->setActive(false);

tempMenuItem=new MenuItem("Paste Item", "Paste");

if(!tempMenu->addMenuItem(tempMenuItem, 4)) return;

if(!tempMenuItem->setHotKey('V')) return;

tempMenuItem->setActive(false);

tempMenuItem=new MenuItem("Clear Item", "Clear");

if(!tempMenu->addMenuItem(tempMenuItem, 5)) return;

tempMenuItem->setActive(false);

tempMenuItem=new MenuItem("Divider 2", "-");

if(!tempMenu->addMenuItem(tempMenuItem, 6)) return;

tempMenuItem=new MenuItem("Select All Item",

"Select All");

if(!tempMenu->addMenuItem(tempMenuItem, 7)) return;

if(!tempMenuItem->setHotKey('A')) return;

tempMenuItem->setActive(false);

tempMenu=new Menu("Style Menu", "Style");

if(!tempMenuBar->addMenu(tempMenu, 2)) return;

tempMenuItem=new MenuItem("Plain Item", "Plain");

//Here is an example of a manu item with a mark next

//to its caption.

tempMenuItem->setMark('Ã');

if(!tempMenu->addMenuItem(tempMenuItem, 0)) return;

//Here are examples of menus with text stylings.

tempMenuItem=new MenuItem("Bold Item", "Bold");

if(!tempMenu->addMenuItem(tempMenuItem, 1)) return;

tempMenuItem->setStyle(bold);

tempMenuItem=new MenuItem("Italic Item", "Italic");

if(!tempMenu->addMenuItem(tempMenuItem, 2)) return;

tempMenuItem->setStyle(italic);

tempMenuItem=new MenuItem("Underline Item",

"Underline");

if(!tempMenu->addMenuItem(tempMenuItem, 3)) return;

if(!tempMenuItem->addCommand(cmdCloseFrontWindow))

return;

tempMenuItem->setStyle(underline);

tempMenuItem=new MenuItem("Outline Item", "Outline");

if(!tempMenu->addMenuItem(tempMenuItem, 4)) return;

if(!tempMenuItem->addCommand(cmdQuit)) return;

tempMenuItem->setStyle(outline);

tempMenuItem=new MenuItem("Shadow Item", "Shadow");

if(!tempMenu->addMenuItem(tempMenuItem, 5)) return;

tempMenuItem->setStyle(shadow);

//This draws the application. It basically calls the

//update functions in the menu bar and all its windows.

//The windows then draw themselves and calls the update

//functions for all its controls. The menu bar does

//likewise with its menus, the menus do likewise with

//their items, the items do likewise with their submenus,

//etc.

myApp->update();

//This tells the command manager object to start polling

//events, processing them, and executing the event

//handler commands that the application returns for

//events. It returns when something in the system tells

//it to stop.

commander->go();

}

 

Screen Shots

This is the initial state when launched.

Here is the Apple menu. When you click the first item, it beeps.

The scroll bar has been played with at this point. Also visible is the Edit menu with its disabled options and menu dividers.

Here is the File menu and its submenu.

Here is the Style menu with its stylized text. The windows have also been moved by dragging them with the mouse.

The close box of the empty second window was clicked so it is now gone. The first window has been moved again and also resized. The user has also clicked on the checkbox.

Bibliography

"Building Application Frameworks: Object-Oriented Foundations of Framework Design"

Mohamed Fayad, Douglas C. Schmidt, Ralph Johnson

Wiley Computer Publishing, New York, 1999

"Building Object-Oriented Frameworks"

Taligent Inc.

A Taligent white paper, 1993

"Design Patterns: Elements of Reusable Object-Oriented Software"

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Grady Booch

Addison-Wesley Longman, Massachusetts, 1994

"Designing Reusable Classes"

Ralph E. Johnson, Brian Foote

Journal of Object-Oriented Programming, June/July 1988, Volume 1, Number 2, pages 22-35

"Designing to Facilitate Change with Object-Oriented Frameworks"

Brian Foote

University of Illinois, Dept. of Computer Science, 1988

"Evolving Frameworks: A Pattern Language for Developing Object-Oriented Frameworks"

Don Roberts, Ralph Johnson

University of Illinois, Dept. of Computer Science, 1996

 

"Leveraging Object-Oriented Frameworks"

Taligent Inc.

A Taligent white paper, 1994

"Modeling Components and Frameworks with UML"

Cris Kobryn

Communications of the ACM, October 2000, Volume 43, Number 10, pages 31-38

"A Pattern Language: Towns, Buildings, Construction"

Christopher Alexander, Murray Silverstein, Sara Ishikawa, Max Jacobson, Shlomo Angel, Ingrid Fiksdahl-King

Oxford University Press, New York, 1977