OBJECT-ORIENTED ANALYSIS AND DESIGNWith application 2nd phần 8 pptx

54 244 0
OBJECT-ORIENTED ANALYSIS AND DESIGNWith application 2nd phần 8 pptx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Chapter 9: Frameworks 371 int unbind(const Item&); Container* bucket(unsigned int bucket); unsigned int extent() const; int isBound(const Item&) const; const Value* valueOf(const Item&) const; const Container *const bucket(unsigned int bucket) const; protected: Container rep[Buckets]; }; Notice the use of Container as a template argument, which allows us to define our abstraction of an open hash table independently of the particular concrete sequence we use. For example, consider the highly elided declaration of the unbounded map, which builds upon the classes Table and Unbounded: Figure 9-12 Support Classes template<class Item, class Value, unsigned int Buckets, class StorageManage> class UnboundedMap : public Map<Item, Value> { Chapter 9: Frameworks 372 public: UnboundedMap(); virtual int bind(const Item&, const Value&); virtual int rebind(const Item&, const Value&); virtual int unbind(const Item&); protected: Table<Item, Value, Buckets, Unbounded<Pair<Item, Value>, StorageManager> > rep; }; Here, we instantiate the class Table with an Unbounded container. Figure 9-12 illustrates the collaboration of these classes. As a measure of the general applicability of this abstraction, we may apply the class Table to our implementation of the Set and Bag classes as well. Tools In this library, the primary use of templates is to parameterize each structure with the kind of item it contains; this is why such structures are often called container classes. As the declaration of the class Table illustrates, templates may also be used to provide certain implementation information to a class. An even more sophisticated situation involves tools that operate upon other structures. As we explained earlier, we can objectify algorithms by inventing classes whose instances act as agents responsible for carrying out the algorithm. This approach follows Jacobson’s idea of a control object, whose behavior provides the glue whereby other objects collaborate within a use-case [16]. The advantage of this approach is that it lets us take advantage of patterns within certain families of algorithms, by forming inheritance lattices. This not only simplifies their implementation, but provides a way to conceptually unify similar algorithms from the perspective of their clients. For example, consider the algorithms that search for patterns within a sequence. A number of such algorithms exist, with varying time semantics: Chapter 9: Frameworks 373 • Simple The structure is searched sequentially for the given pattern; in the worst case, this algorithm has a time complexity on the order of O(pn), where p is the length of the pattern, and n is the length of the sequence. • Knuth-Morris-Pratt The structure is searched for the given pattern, with a time complexity of O(p + n); searching requires no backup, which makes this algorithm suitable for streams. • Boyer-Moore The structure is searched for the given pattern, with a sublinear time complexity of O(c * (p + n)), where c < 1 and is inversely proportional to p. • Regular expression The structure is searched for the given regular expression pattern. There are at least three common features of these algorithms: they all operate upon sequences (and hence expect certain protocols from the objects they are searching), they all require the existence of an equality function for the items being searched (because the default equality operation may be insufficient), and they all have substantially the same signature for their invocation (they require a target, a pattern, and a starting index). The need for an equality operation requires some explanation. Suppose, for example, we have an ordered collection of personnel records. We might wish to search this sequence for a certain pattern of records, such as groups of three records all from the same department. Using the operator== for the class PersonnelRecord won't work, because this operator probably tests for equality based upon some unique id. Instead, we must supply a special test for equality to our algorithm that queries the department of each person (by invoking a suitable selector). Because each pattern-matching agent requires an equality function, we can provide a common protocol for setting the function as part of some abstract base class. For example, we might use the following declaration: template<class Item, class Sequence> class PatternMatch { public: PatternMatch(); PatternMatch(int (*isEqual)(const Item& x, const Item& y)); virtual ~PatternMatch(); virtual void setIsEqualFunction(int (*)(const Item& x, const Item& y)); virtual int match(const Sequence& target, const Sequence& pattern, unsigned int start = 0) = 0; virtual int match(const Sequence& target, unsigned int start = 0) = 0; protected: Sequence rep; Chapter 9: Frameworks 374 int (*isEqual)(const Item& x, const Item& y); private: void operator=(const PatternMatch&) {} void operator==(const PatternMatch&) {} void operator!=(const PatternMatch&) {} }; Notice that we again use the idiom for assignment and test for equality, which prevents objects of this class or its subclasses from being assigned or compared to one another. We do so because these operations have no real meaning when applied to such agent abstractions. We can next devise concrete subclasses such as for the Boyer-Moore algorithm: template<class Item, class Sequence> class BMPatternMatch : public PatternMatch<Item, Sequence> { public: BMPatternMatch(); BMPatternMatch(int (*isEqual)(const Item& x, const Item& y)); virtual ~BMPatternMatch(); virtual int match(const Sequence& target, const Sequence& pattern, unsigned int start = 0); virtual int match(const Sequence& target, unsigned int start = 0); protected: unsigned int length; unsigned int* skipTable; void preprocess(const Sequence& pattern); unsigned int itemsSkip(const Sequence& pattern, const Item& item); }; Chapter 9: Frameworks 375 Figure 9-13 Pattern Matching Classes The public protocol of this class implements that of its superclass. In addition, we provide two member objects and two member helper functions. One of the secrets of this class is the creation of a temporary table that it uses to skip over long, unmatched sequences; these members serve to implement this secret. As figure 9-13 illustrates, we may build a hierarchy of pattern matching classes. In fact, this kind of hierarchy applies to all of the tools in our library, giving it a regular structure that makes it far easier for clients to find the abstractions that best fit their time and space semantics. 9.4 Maintenance One fascinating characteristic of frameworks is that - if well-engineered - they tend to reach a sort of a critical mass of functionality and adaptability. In other words, if we have selected the right abstractions, and if we have populated the library with a set of mechanisms that work together well, then we will find that clients soon discover means to build upon the library in ways its designers never imagined or expected. As we discover patterns in the ways that clients use our framework, then it makes sense to codify these patterns by formally making them a part of the library. A sign of a well-designed framework is that we can introduce these new patterns during maintenance by reusing existing mechanisms and thus preserving its design integrity. One such pattern of use for this library involves the problem of persistence. We might find clients who don't want or need the full power of an object-oriented database, but who from Chapter 9: Frameworks 376 time to time need to save the state of structures such as queues and sets, and then reconstruct these objects in a later invocation of the program, or perhaps from a different program altogether. Because this pattern of use is so common, it makes sense for us to augment our library with a simple persistence mechanism. Figure 9-14 Persistence Classes We will make two assumptions about this facility. First, clients are made responsible for providing a stream to which items are put and from which items are restored. Second, clients are responsible for ensuring that: items have the behavior necessary for them to be streamed. Two alternate designs for this facility come to mind. We could devise a mixin class that supplied persistence semantics; this is the approach used by many object-oriented databases. Alternately, we could devise a class whose instances act as agents responsible for streaming various structures. As part of our exploration, we might try both approaches, to see which is a better fit. As it turns out, the mixin style doesn't work well for this particular simple form of persistence (although it is well suited for full-functioned object-oriented databases). Using a mixing style requires that clients who mix in an abstraction plug it together with weir user-defined class, Chapter 9: Frameworks 377 often by redefining certain mixin helper functions For such a simple agent, however, clients would end up writing more code than if they crafted the mechanism by hand. This is clearly not acceptable, and so we turn to the second approach, which requires little more than an instantiation on the part of the client. Figure 9-14 illustrates our design for this mechanism, in which we provide persistence through the behavior of a separate agent. The class Persist is a friend of the class Queue, but we can defer this association by introducing the following friend declaration in the Queue class: friend class Persist<Item, Queue<Item> >; In this manner, friendship is established only at the time we instantiate the Queue class. In fact, by introducing a similar friend declaration in every abstract base class, we can reuse the class Persist for every structure in the library. The parameterized class Persist provides the operations put and get, as well as operations for setting its input and output streams. We may capture this abstraction in the following declaration: template<class Item, class Structure> class Persist { public: Persist(); Persist(iostream& input, iostream& output); virtual ~Persist(); virtual void setInputStream(iostream&); virtual void setOutputStream(iostream&); virtual void put(Structure&); virtual void get(Structure&); protected: iostream* inStream; iostream* outStream; }; The implementation of this class depends upon its friendship with the class Structure, which is imported as a template argument. Specifically, Persist depends upon the existence of the structure’s helper functions: purge, cardinality, itemAt, lock, and unlock. Here the regularity of our library pays off: since every Structure base class provides these helper functions, we can use the class Persist without any change to the library’s existing architecture. Consider for example, the implementation of Persist::put: template<class Item, class Structure> Chapter 9: Frameworks 378 void Persist<Item, Structure>::put(Structure& s) { s.lock(); unsigned int count = s.cardinality(); (*outStream) << count << endl; for (unsigned int index = 0; index < count; index++) (*outStream) << s.itemAt(index); s.unlock(); } This operation uses our earlier locking mechanism, so that its semantics work for both the guarded and synchronized forms. The algorithm proceeds by streaming out the size of the structure and then its individual elements in order. Similarly, the implementation of Persist::get reverses this action: template<class Item, class Structure> void Persist<Item, Structure>::get(Structure& s) { s.lock(); unsigned int count; Item item; if (!inStream->eof()) { (*inStream) >> count; s.purge(); for (unsigned int index = 0; (index < count) && (!inStream- >eof()); index++) { (*inStream) >> item; s.add(item); } } s.unlock(); } To use this simple form of persistence consistency across the library, the client thus has only to instantiate one additional class per structure. Building frameworks is hard. In crafting general class libraries, one must balance the needs for functionality, flexibility, and simplicity. Strive to build flexible libraries, because you can never know exactly how programmers will use your abstractions. Furthermore, it is wise to build libraries that make as few assumptions about their environment as possible, so that programmers can easily combine them with other class libraries. The architect must also devise simple abstractions, so that they are efficient, and so that programmers can understand them. The most profoundly elegant framework will never be reused, unless the cost of understanding it and then using its abstractions is lower than the programmer’s perceived cost of writing them from scratch. The real payoff comes when these classes and mechanisms get reused over and over again, indicating that others are gaining leverage from the developer’s hard work, allowing them to focus on the unique parts of their own particular problem. Chapter 9: Frameworks 379 Further Readings Biggerstaff and Perlis [H 1989] provide a comprehensive treatment of software reuse. Wirfs- Brock [C 1988] offers a good introduction to object-oriented frameworks. Johnson [G 1992] examines approaches to documenting the architecture of frameworks through the recognition of their patterns. MacApp [G 1989] offers an example of one specific, well-engineered, object-oriented application framework for the Macintosh. An introduction to an early version of this class library may be found in Schmucker [G 1986]. In a more recent work, Goldstein and Alger [C 1992] discuss the activities of developing object-oriented software for the Macintosh. Other examples of frameworks abound, covering a variety of problem domains, including hypermedia (Meyrowitz [C 1986]), pattern recognition (Yoshida [C 1988]), interactive graphics (Young [C 1987]), and desktop publishing (Ferrel [K 1989]). General application frarneworks include ET++ (Weinand, [K 1989]) and event-driven MVC architectures (Shan [G 1989]). Coggins [C 1990] studies the issues concerning the development of C++ libraries in particular. An empirical study of object-oriented architectures and their effects upon reuse may be found in Lewis [C 1992]. CHAPTER 10 380 Client/Server Computing: Inventory Tracking For many business applications, a company will use an off-the-shelf database management system (DBMS) to furnish a generic solution to the problems of persistent data storage, concurrent database access, data integrity, security, and backups. Of course, any DBMS must be adapted to the given business enterprise, and organizations have traditionally approached this problem by separating it into two different ones: the design of the data is given over to database experts, and the design of the software for processing transactions against the database is given over to application developers. This technique has certain advantages, but it does involve some very real problems. Frankly, there are cultural differences between database designers and programmers, which reflect their different technologies and skills. Database designers tend to see the world in terms of persistent, monolithic tables of information, whereas application developers tend to se he world in terms of its flow of control. It is impossible to achieve integrity of design in a complex system unless the concerns of these two groups are reconciled. In a system in which data issues dominate, we must be able to make intelligent trade-offs between a database and its applications. A database schema designed without regard for its use is both inefficient and clumsy. Similarly, applications developed in isolation place unreasonable demands upon the database and often result in serious problems of data integrity due to the redundancy of data. In the past, traditional mainframe computing raised some very real walls around a company's database assets. However, given the advent of low-cost computing, which places personal productivity tools in the hands of a multitude of workers, together with networks that serve to link the ubiquitous personal computer across offices as well as across nations, the face of information management systems has been irreversibly changed. Clearly a major part of this fundamental change is the application of client/server architectures. As Mimno points out, “The rapid movement toward downsizing and client-server computing is being driven by business imperatives. In the face of rapidly increasing competition and shrinking product cycles, business managers are looking for ways to get products to market faster, increase services to customers, respond faster to competitive challenges, and cut costs” [1]. In this chapter, we tackle a management information system (MIS) application and show how object- [...]... assembling and then shipping an order • Generating invoices and tracking accounts receivable • Generating supply requests and tracking accounts payable In addition to automating much of the warehouse’s daily workflow, the system must provide a general and open-ended reporting facility, so that the management team can track sales trends, identify valued and problem customers and suppliers, and carry... of four components: 98 Not unlike the question of what is and what isn't object-oriented 386 Chapter 10: Client/Server Computing • Presentation logic • Business logic • Database logic • Database processing The part of an application that interacts with an end-user device such as a terminal, a bar cede reader, or a handheld computer Functions include “screen formatting, reading, and writing of the screen... formatting, reading, and writing of the screen information, window management, keyboard, and mouse handling.” The part of an application that uses information from the user and from the database to carry out transactions as constrained by the rules of the business The part of an application that “manipulates data within the application Data manipulation in relational DBMSs is done using some dialect of the... building full of computers and a swarm of people to support it is not very cost-effective Database design has many parallels with object-oriented development In database technology, design is often viewed as an incremental and iterative process involving both logical and physical decisions [9] As Wiorkowski and Kull point out, "Objects that describe a database in the way that users and developers think about... relations are needed and what their attributes should be? This is the database design problem" [19] As it turns out, identifying the key abstractions of a database is much like the process of identifying classes and objects in object-oriented development For this reason, in dataintensive applications such as the inventory-tracking system, we start with an object oriented analysis and use its process... in the same fashion as a mainframe in a master-slave architecture Distributed applications are more complex than nondistributed applications [6] We mitigate these risks through the use of an object-oriented architecture and development process Scenarios Now that we have established the scope of our system, we continue our analysis by studying several scenarios of its use We begin by enumerating a number... database, "inconsistency can be reduced, standards can be enforced, security restrictions can be applied, and database integrity can be maintained" [8] Designing an effective database is a difficult task because there are so many- competing requirements The database designer must not only satisfy the functional requirements of the application, but must also address time and space factors A time-inefficient... mean, and where they are located) rather than procedural knowledge (how things happen) The soul of our design will be found in the central concerns of object-oriented development: the key abstractions that form the vocabulary of the problem domain and the mechanisms that manipulate them Business demands require that our inventory-tracking system must be, by its very nature, open-ended During analysis, ... engineering (CAE) and computer-aided software engineering (CASE) applications, for which we must manipulate significant amounts of data with a rich semantic content For certain applications, object-oriented databases can offer significant performance improvements over traditional relational databases Specifically, in those circumstances where we must perform multiple joins over many distinct tables, object-oriented. .. context of an object-oriented architecture reduces our development risk Relational database technology is much more mature, available across a wider variety of platforms, and often more complete, offering solutions to the issues of security, versioning, and referential integrity Furthermore, a company may have significant capital investment in people and tools supporting the relational model, and so for . 1 986 ]), pattern recognition (Yoshida [C 1 988 ]), interactive graphics (Young [C 1 987 ]), and desktop publishing (Ferrel [K 1 989 ]). General application frarneworks include ET++ (Weinand, [K 1 989 ]). Further Readings Biggerstaff and Perlis [H 1 989 ] provide a comprehensive treatment of software reuse. Wirfs- Brock [C 1 988 ] offers a good introduction to object-oriented frameworks. Johnson. database and its applications. A database schema designed without regard for its use is both inefficient and clumsy. Similarly, applications developed in isolation place unreasonable demands upon

Ngày đăng: 12/08/2014, 21:21

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan