NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] - نسخه متنی

Matthew MacDonald

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید




































Developing in Tiers



The basic principle of three-tier design is simple. An application is divided into three distinct subsystems. Every class belongs to only one of these three partitions, and performs just one kind of task. The three tiers are usually identified as:





A presentation layer, which converts a user's actions into tasks and outputs data using the appropriate controls.





A business layer, where all the calculations and processing specific to the individual business are carried.





An underlying data layer, which shuttles information back and forth from the database to the business objects.





An object in one tier can only interact with the adjacent tiers, as shown in Figure 2-6.




Figure 2-6: Three-tier design


Almost everyone agrees that this sort of structure is the best way to organize an application, but it's not always easy to implement this design. Though the schematic looks simple, modern user interfaces are usually quite complicated, and sometimes make assumptions or have expectations about the way they will receive information. The result is that everyone recommends this model, but very few developers successfully follow it.



Problems with Three-Tier Design



Before you can solve the problems of three-tier design, you have to understand what these problems are. The problems, although not insurmountable, are found in every tier.


The presentation tier



Though it doesn't explicitly state it, three-tier design really requires a fair degree of consistency among user interface controls. In the real world, this consistency doesn't exist. For example, making what is conceptually a minor change-like substituting a ListView control for a DataGrid-requires a totally different access model. DataGrids are filled exclusively by data binding. ListViews, on the other hand, act like a collection of items. To get information into other ListView columns, you have to add a collection of fields to each individual item. These quirks are easy enough to master (and you sort through them in Chapter 6), but they don't make it possible to create business objects that can quickly and efficiently fill common controls.




The business tier



In three-tier design, it's assumed that the user interface is isolated from the underlying data source. Information for a control is requested through a layer of business objects. This can be a problem if you create business objects that try to model real-world entities. For example, if you need to retrieve a list of customers and some summary information based on all customer orders, you would be crazy to try and work with individual Customer and Order objects. Consider the process:





The user interface layer requests some information from the business layer.





The business layer creates hundreds of Customer and Order objects by retrieving information from the data layer. These objects are organized into large collections. Each property for each object is set according to the corresponding field in the corresponding row.





The user interface layer iterates through all these objects, withdrawing and calculating the relatively simple information it needs to actually display the required rows. Each field is extracted separately as an individual property, which requires a separate line of code. The code is very readable, but far from elegant or efficient.





Some three-tier designs get around this by creating a very thin business layer that might just consist of a pile of utility functions for retrieving rows as arrays. But is this really a good object-oriented design?




The data tier



Keeping the data tier separate from the business tier is another battle. To optimize performance, databases in enterprise applications usually rely on stored procedures, views, and other optimized ways to retrieve and update data. However, the user interface tier can't be built in a database-friendly way, because it is designed to be completely generic. It also can't rely on tricks that programmers' love, like dynamically generated SQL statements, because it is supposed to be completely isolated from the data tier. The result is a tradeoff, where you can favor any one of the following approaches:





Create a "thin" business layer that uses methods that correspond very closely to stored procedures and other database-specific parameters. In fact, some programs use business objects that are just thin wrappers on top of a live cursor-based database connection (like an ADO recordset). Unfortunately, this business layer requires significant rework if the database changes.





Create an average business layer that lets the user interface retrieve whatever data it wants. The business tier relies on accessing the database using generic SQL statements. It's very expandable and generic, but database performance will be terrible.





Create a "thick" business layer that tries to match requests from the user interface with an optimized execution path for a specific database. With a little luck and careful coding, performance could be as good as the first option, and the layer could be nearly as generic as the second. However, writing this tier is a major programming undertaking that takes exponentially more time.









Three-Tier Design Consistency



There's nothing explicitly wrong with three-tier design, but it isn't a magic solution. For one thing, it's a rather vague recommendation that has more guidelines than requirements. Developers can interpret it any way they want.


For example, consider an application that reads customer information from a database and displays it in an attractive list control. At first glance, it seems like a straightforward task. But consider the number of different ways it could be modeled with objects:





A CustomerData class fetches information from the database, and returns it as a DataSet. Your code then manually reads the DataSet and adds the information to a list control.





A CustomerData class fetches information from the database. You also create a customized CustomerList control class that knows how to fill itself using the DataSet it receives from CustomerData.





A CustomerData class fetches information from the database. However, the CustomerData class also receives a reference to the list control that needs to be filled. The CustomerData class has the built-in smarts to know how to fill the list control's collection of items.





A CustomerData class fetches information from the database. A special helper class, FillListFromDataSet handles the conversion of the information in the DataSet to information in the generic list control.





Which approach is the best one? It's difficult to say. The third option is somewhat suspicious, because it seems that the CustomerData class is being given additional responsibilities beyond the scope it was designed for. Some of the other scenarios make assumptions about the appropriate way to exchange data. But the greatest problem with all of these examples is that there is no guarantee that the other classes in the application will follow this pattern.



Tip


An object-oriented framework sets out rules that determine how objects will interact and communicate.When creating a user interface, you have to develop your framework at the same time that you plan your individual objects.






Fixing Three-Tier Design



By now you have probably realized that the most important decision you can make is to define how your user interface objects should interact. This is the simplest way to improve your designs without adopting a single specific solution.


To get a better idea about how you might apply three-tier design in the .NET world, consider the simple application below. It uses a TreeView that displays a list of product categories drawn from a database. When you expand a category node, you see all the relevant products. When a record is selected, the corresponding information appears on a list on the left (see Figure 2-7).




Figure 2-7: An intelligent TreeView database browser


There are several important considerations:





The whole tree can't necessarily be filled at once. (Whether it actually should depends on the size of the database.) The solution needs the flexibility to be able to fill parts of the trees "just in time," and potentially make multiple trips to the database. An even better solution might make use of threading to asynchronously fetch and add results, or create some kind of paged list.





The component that returns the data can't assume that the information is destined for a TreeView-the design may change, or the same information may be reused in multiple locations.





The TreeView needs to be able to accept the data in an easily interpretable format. It shouldn't need to recognize specific database tables and fields by their "real" names.





Obviously, there are different ways this application can retrieve information from the database. It could use custom data objects, but that would require category and product objects that use multiple property procedures. The user interface code would then have to retrieve these properties specifically by name, and decide on an appropriate order. For some applications, this approach would work, but in this case it would choke a relatively simple browser with extra classes and code.





Using the DataSet



Instead, information in our example is sent in a neat package called the DataSet. The DataSet is found in the System.Data class, and is often referred to as a part of Microsoft's ADO.NET technology. Unlike the objects in earlier ADO, the DataSet is an ideal solution for information transfer in an application.





It's much more generic. All .NET languages support the DataSet.





It's inherently portable across language and platform boundaries, because it allows you to quickly swap information into XML (which is another great way to shuffle information around in a program).





It's inherently disconnected. Unlike some business objects created in ADO, that are really just Rowset objects with hidden cursors, the DataSet is a discrete copy of information, which makes it easy to transfer and even hold for as long as you want.





The DataSet loosely mirrors a database. Your business layer can easily add column mapping and perform other tricks, making sure the user interface layer gets exactly what it expects, regardless of the underlying data source.





The DataSet can contain several tables at once. It can contain data relationships, which makes it easy to navigate through the data, but these relationships are independent of whatever relationships exist in the actual data source.





It has update possibilities. If you want to hold onto the DataSet, you can make use of built-in methods to withdraw the changed rows and resubmit them to the appropriate business object.





In other words, the DataSet removes some of the headaches of three-tier design. It allows a way to transfer information that's much more lightweight than dedicated classes, but not necessarily tied to a specific database. You can browse through its rows and fields as collections, which makes it easy to add information to list-like controls without needing to look for specific field names. Of course, when you want to start formatting column widths and setting column orders, your user interface may need to know a fair amount of information about the data. As long as you place this information in a central repository (generally a resource class with static members), your user interface will be extensible and easy to change.



Note


Where's the code? In this chapter, the emphasis is on concepts. If you really need to see the specific implementation details-which use a combination of data embedding, a dedicated data class, and the ADO.NET data objects-skip ahead to Chapter 9, which shows this code and several other data-enabled user interface controls.






Validation and Business Objects



Before continuing, allow me to debunk one myth about business objects. It's sometimes argued that business objects are an ideal way to store and transfer data because they can provide integrated error checking. In all honesty, the value of this error checking is limited. First, it's duplicating information in the database, which adds extra overhead for writing the code. More important, the error checking happens when the value is assigned, which is too late for it to be useful in the actual user interface. As you learned in the last chapter, the best thing you can do for users is to restrict their ability to make mistakes. That means you have to act on an error as soon as it happens, or better yet, forbid it entirely.


No matter what, your user interface has to be designed with some business rules built-in (for example, forbidding letters in a text box that represents an invoice amount). Your data source performs the final error checking. There's not much use in adding new layers of error checking between these two (except to verify that your client code is behaving correctly). Even the best three-tier design can't escape the need to import business rules into the interface.





Other Types of Application



The TreeView DataSet example above is a viable solution for many traditional database applications, which allow information to be reviewed, analyzed, and modified. Other types of applications may need other allowances.





For example, a three-dimensional action game breaks down the barrier between the user interface and the "business" layer. Every object in the game is tightly bound to its on-screen appearance, and also makes use of a slew of programming algorithms to calculate how it moves and what it does. Object communication for this type of application is completely different.





As another example, consider a live running tutorial. This kind of application might lend itself most easily to three-tier design. The information in a file is completely processed and delivered in a business object. The user interface is consistent, probably using a single window with several custom classes and a main controller. The controller retrieves information from the business objects, and delivers it to the appropriate user interface class, which handles the display.





Or consider document-based applications. These applications are also easier to implement as three-tier designs, because they are based around a well-defined view that allows you to modify a single underlying data object. There just isn't the variety of information and ways of interacting with it that makes many database or hybrid applications more difficult to program and organize.








/ 142