Rapid Creation of Reusable Architectures

Contents:
Abstract
Reusable architectures
Rapid architectures
Processing business logic
The framework
Improvements
Summary
About the author
Rate this article
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)

Jim Ruehlin (jruehlin@us.ibm.com)
Senior Technical Specialist, IBM
11 Aug 2004

After reading this paper you'll have an idea of how to create your own reusable framework using patterns and IBM Rational XDE 2003.

Abstract
Economically significant reuse has always been a holy grail of software engineering. The technology of reuse has matured from copying and pasting source code, to fine-grained reuse at the class level, to coarse-grained large-scale reuse at the framework and architectural levels. The economically significant reuse that occurs at the architectural level is a high-leverage strategic activity almost all development projects should strive to achieve. After reading this paper, you'll have an idea of how to create your own reusable framework using patterns and IBM® Rational® XDE® v2003.

Note: All models and code are described using XDE v2003 for .NET. A framework similar to the one presented here could be created for the J2EE environment. Code samples are written in C# so they should be clear to any C++ or Java programmer.

Reusable architectures
Around 20 years ago software engineering was compared to old-time blacksmithing. When you wanted to build your house in the good old days, you went to the blacksmith for all the metal parts that had to be used. Everything from iron sconces to door hinges to nails were each built by hand, one by one. So it was with software engineering - nearly all code was new code, each algorithm and piece of the design was written specifically for the system being built, and next time they would probably be built again. Code reuse (the only type of reuse being attempted at the time) was seen as a way to buy hundreds of nails at the hardware store rather than making each nail by hand. The result would be shorter development cycles with more reliable code.

The introduction of object oriented programming in the mid 1980s in the form of commercially viable languages such as C++ (Smalltalk was around earlier but wasn't widely used in business environments) directly supported code reuse through classes. Some important, measurable gains in reuse were achieved, but the predicted "big leap forward" in productivity didn't happen as hoped since developers often didn't have any way of determining which classes should be reused. Certainly some code could be reused, making the writing of future code faster and more reliable. But it wasn't until the advent of visual modeling and code generation tools that practitioners began to see that reusing design — concepts and ideas — provided greater economic return than reusing code. They realized that the benefits of reuse at an architectural and design level would provide exponentially more value.

Now the blacksmith analogy is more powerful. With the invention of design patterns, the idea is to reuse designs since it's easy and inexpensive to generate code from design models. Now, instead of reusing nails, we're reusing foundations and load-bearing walls like pieces of a pre-fabricated building. We're no longer homebuilders, we're home designers. Instead of getting dozens of your neighbors to pound boards together to build a barn, you can now buy the walls and roof and have a few workmen assemble it in a few hours.

So it is with architectural frameworks. Why not start out with 20 percent, or 30 percent, or 60 percent, of your architecture already in place? Why not use the same architecture from application to application, making it easier for software engineers to work with successive applications? Microsoft did this when they created the MFC library. Sun did this when they came up with Java and the J2EE standards, and Microsoft is trying doing it again with .NET. It's simply easier, faster, less expensive, and more reliable to build your Web application on J2EE then it is build a Web application from scratch, like they did in the '90s. In other words, a framework like J2EE provides economically significant reuse for building Web applications.

"Good, Fast, Cheap: choose two" is an old engineering koan. One of the three must be sacrificed for the sake of other two. But reusing architectural frameworks is a way to reduce the risk and get better at all three.

Rapid architectures
Large industrial-strength architectures such as J2EE were created over time with a significant amount of experience and resources. J2EE leverages reusability across projects, companies, and industries. Thousands of applications reuse the work that's gone into J2EE so the large amount of effort put into creating the architecture still provides significant ROI. But what if we're not able to use J2EE, .NET, or some other industrial strength architectural framework? Or what if we need a reusable architecture for our organization built on top of J2EE or .NET? We may only be reusing the architecture a few times, or dozens of times, instead of thousands of times. We need the ability to create a robust and reusable architecture, but build it rapidly and inexpensively enough to give us payback for our efforts with a minimum of reuse.

In this article we'll use design patterns to rapidly create a framework, which in this case is a pattern of patterns. Applying these patterns in XDE will generate the bulk of our code, so most of the code that implements the framework will be untouched by human hands. Even if this were the only benefit of using patterns we'd still have more reliable code. There will be a few hand-written lines of "glue code" to string things together. In the end we'll have a 3-tier architecture that processes business logic in a repeatable, predictable, and robust (component based) framework. This framework can be reused (applied as a single pattern rather than re-typing code) to create other application architectures even faster. Developers who work on this framework once will not need to go through a learning curve on subsequent applications that use the same framework. And improvements or fixes to the architecture can be rapidly propagated across applications by re-applying patterns rather than hand-modifying code multiple times.

Processing business logic
The framework we'll create is a simple 3-tier framework that processes business logic using three patterns: the 3-layer architecture pattern, the Singleton pattern, and the Command pattern. The Singleton and Command patterns are from the Gang of Four "Design Patterns" book and ship with XDE v2003. The Command pattern used in this article has been simplified for clarity.

First we'll look at the framework's behavior. Figure 1 is a sequence diagram that describes how the framework will process business logic.

Figure 1: Business Logic Behavior
(click here to enlarge)

Business objects are colored in red. User interface objects are blue. The sequence starts when a user requests a command (an application function) to be executed by interacting with a user interface object, in this case the main form of the application. The form is part of the presentation layer so it doesn't implement any business logic. But it knows enough to pass the request to the business layer. The form will do this through an interface to the business logic layer that is realized by the BusinessLayerProxy class. This is a controller class that has the responsibility of managing the execution of the business logic.

The BusinessLayerProxy creates or accesses an instance CommandFactory. The CommandFactory is a Singleton, a class that is guaranteed to have at most one instance (object) of itself in the application. Using the Singleton pattern we guarantee that no matter how many times the BusinessLayerProxy tries to create a CommandFactory, only one instance will exist. CommandFactory already knows if it has an instance, and if so doesn't create another instance.

BusinessProxy then asks CommandFactory to create the desired business logic object (command) so the business can be executed. This is CommandFactory's only job — to know how to create the appropriate business logic. In our example application, an integer ID is passed to CommandFactory so it can create the correct Command object. This simple tactic has the advantage of being easy to understand for the purposes of this article. In real life we may need a more robust (and complex) way of identifying which business logic needs to be created. Using other patterns to identify the business logic (such as Abstract Factory or Factory Method) could reduce maintenance overhead and increase usability.

All business logic is encapsulated in Command objects by applying the Command pattern. CommandFactory creates an instance of the desired business logic and passes an interface to the command back to the BusinessLayerProxy. BusinessLayerProxy never knows what command it's executing, only that it's executing some business logic through the ICommand interface (notice the return value from CommandFactory). Once the BusinessLayerProxy has the interface it executes the command via the execute() operation. This is an application of the Command pattern.

There are some good OO design practices being used in this example. Each class should do just one thing and do it well, so the BusinessLayerProxy manages execution flow, the CommandFactory knows how to create commands, and each command class encapsulates — or is the only gateway to — the logic for a single application service. The business layer is only accessible via the business layer interface, isolating the presentation layer from changes in the business layer. These applications of encapsulation, modularity, and polymorphism are implemented by the patterns. We can leverage these good design practices for free by taking advantage of XDE's pattern expansion capabilities.

The framework
We start with the beginnings of a project structure (see Figure 2). This structure is an application of the 3-layer pattern, which separates application logic into Presentation, Business, and Integration/Persistence categories. The code in a layer can only access the layer beneath it, and only through an interface (see Figure 3). For this article, we're concerned with the Business layer and to a lesser extent the Presentation layer. In the Business layer we already have a BusinessLayerProxy, which realizes the IBusinessLayer interface and acts as a manager or controller for the entire business layer (see Figure 4).

Figure 2: Initial Project Structure

The 3-layer Pattern shown here is what the pattern looks like before it's applied. Javascript is used as a placeholder for the parameter name. The Javascript is replaced with the actual package/subsystem name when the pattern is applied, for instance replacing the text "<%=bizlayer%>" with "BusinessLayer".

Figure 3: 3-layer Pattern

The application's main form, "Form1," is in the Presentation layer. Since Form1 is part of the Presentation layer it must access the business layer through its interface (see Figure 4). There are three message handlers (the ..._Click() operations) in Form1 that will all use the association, "theBusinessLayer", to access business logic.

Figure 4: Main Form Accessing Business Logic

After synchronizing the code for the model shown in Figure 4 we can access business logic through "theBusinessLayer". The "glue code" we need to write for Form1 is just a line in the message handler to request the business layer execute the appropriate logic. The ID "1" is passed to the business layer so the proper business logic can be identified.

private void Boltz_Click(object sender, System.EventArgs e)
	{
		theBusinessLayer.execute(1);
	}

Now that we've accessed the business layer we need to create the CommandFactory class. We do this by applying the Singleton pattern and creating a new class, CommandFactory, as the singleton. The Singleton pattern is shown in Figure 5.

Figure 5: Singleton Pattern

The text "Singleton" is replaced with "CommandFactory" when we apply the pattern. "Client" becomes "BusinessLayerProxy". GetUniqueInstance() is a static method that returns an instance of CommandFactory. Internally, if an instance of CommandFactory doesn't exist then GetUniqueInstance() will create one. Otherwise it will return the existing instance of CommandFactory. The only way to obtain an instance of CommandFactory is through the GetUniqueInstance() operation (notice the Singleton constructor is private).

Now the BusinessLayerProxy has access to the CommandFactory. We need to identify where the business logic will live using the Command pattern, create that command in CommandFactory when the BusinessLayerProxy requests it, and finally execute that logic in BusinessLayerProxy.

A simplified version of the Command pattern is shown in Figure 6. All concrete commands — all business logic — realize the Command interface. The execute() operation is where the business logic is implemented. In a real application the execute() operation would likely make use of collaborations of classes to perform the necessary business logic. An Invoker executes the command via the interface, and a Client creates the command, passing a reference to the interface back to the Invoker. For our purposes, the Command interface is the existing ICommand interface (see project structure in Figure 2), the Invoker is always BusinessLayerProxy and the creator is always CommandFactory. The applied pattern is shown in Figure 7.

Figure 6: Simplified Command Pattern


Figure 7: Applied Command Pattern

Every command (business logic) is added using the Command pattern. A new ConcreteCommand will be created each time the Command pattern is applied (e.g. the FirstCommand class), and the other three classes are always reused. This provides a very predictable way to add, modify, and debug business logic.

We write a few lines of "glue code" to hook the CommandFactory to the BusinessLayerProxy. We only need to do this once. We also write a couple of lines of code to access FirstCommand from CommandFactory. We'll need to do this each time a new command is added due to the simplified architecture we're using in this example.

We added code to Form1 to access the business layer. Here's the code in the BusinessLayerProxy to request and execute the business logic:

public void execute(int ID)
	{
		GetCommandFactory();
		theCommandFactory.getCommand(ID).execute();
	}

GetComandFactory() assures the CommandFactory is initialized. getCommand(ID) causes CommandFactory to create the command object (see code below), and execute() is the operation in ICommand that performs the business logic.

Here's the code we write in CommandFactory that calls the appropriate business logic:

ICommand theICommand;
public ICommand getCommand(int CommandID)
	{
		theICommand = null;

		switch (CommandID)
		{
			1: theICommand = new FirstCommand();
			break;

			2: // put the next command here
			break;
		}

		return theICommand;
	}

The last thing to do is write the custom business logic in the execute() operation of FirstCommand. Voila! The business logic is properly encapsulated, the business layer has a simple and reusable method for processing the business logic, and a framework is now defined that you can use to create other applications. Re-applying the entire framework next time will be even faster than this first time.

A pattern of patterns can be created so the framework can be transported and re-applied elsewhere (see Figure 8). In XDE you can create this pattern of patterns and export it as a Reusable Asset Specification. Other projects can import the framework and re-use it by applying the entire framework as a single pattern, or applying the patterns individually.

Figure 8: Business Logic Framework (pattern of patterns)

Improvements
An "Undo" feature can be added to this framework relatively easily. An undo() operation can be added to ICommand and implemented in each ConcreteCommand. Instead of deleting the command object after the business logic has been executed, the BusinessLayerProxy could place it on an undo queue. When Undo is called by the user, the Command object at the top of the queue would be pulled off and its undo() operation called. This way undo logic is encapsulated with the same business logic that originally transformed the data.

Logging capabilities can be easily added to this framework. Snippets of parameterized code called "code templates" can be added, or "bound" to class operations in XDE by right-clicking the operation. Binding a code template to an operation in a pattern will cause that code to be generated when the code and model are synchronized. Code that logs information to a file can be added to the execute() operation of ConcreteCommand in the Command pattern. Every command class created from the Command pattern would write information to the log. This can include the name of the command class or any parameterized information in the pattern. Adding this functionality to existing command classes can be done with just a few mouse clicks by re-applying the Command pattern after the code template has been bound to the execute() operation.

A framework could even be created that serves as an abstraction layer on top of J2EE, .Net, or other industrial strength architectural frameworks. Developers would create applications using the custom framework, which in turn would create classes appropriate to the underlying architecture in a way that's largely transparent to the designer and implementer. This last example could be very useful to organizations running applications on architectures from multiple vendors, but it would be a serious undertaking.

Summary
Reuse of architecture and design provides significant efficiencies in coding, maintenance, training, and understandability. Relying on frameworks that have already been validated in production applications shortens development time, reduces cost, and increases product quality. In this paper, we stepped through an example of using 3-layer, Singleton, and Command patterns to rapidly create a framework for processing business logic that can be reused in many different circumstances. Very little human generated code was needed to support the framework, making it easy to transport from one application to another and improving code quality from the start. The conclusion is that reusable frameworks provide a competitive advantage to development organizations.

About the author
Jim Ruehlin has been a Senior Technical Specialist at IBM Rational for almost 5 years. His focus is object oriented design, visual modeling, and systems engineering.
《设计模式:可复用的面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是一本经典的计算机科学书籍,由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides联合编写。 该书首次系统地介绍了23种常见的设计模式,这些模式是面向对象软件开发的重要指导和实践。设计模式是一种对重复出现的问题的解决方案,通过它们可以提供一种通用、可重用和可扩展的设计方法,用于解决软件系统中的常见问题。 《设计模式:可复用的面向对象软件的基础》描述了每个设计模式的结构、目的、应用场景和实现方式。它通过示例代码和详细解释来帮助读者理解每种模式的用途和优缺点。 这本书的主要贡献之一是将设计模式分为三个类别:创建型、结构型和行为型模式。其中,创建型模式关注如何通过不同的方式创建对象,结构型模式关注如何组合对象以形成新功能,行为型模式关注对象之间的通信和协作方式。通过这种分类方式,读者可以更好地理解和应用设计模式。 《设计模式:可复用的面向对象软件的基础》已成为软件工程师必读的经典之作。它提供了一种设计思维的范式,可以帮助开发人员提高软件的扩展性、可读性和可维护性。无论是初学者还是经验丰富的开发人员,阅读并理解设计模式都对他们的软件设计和开发能力有很大的帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值