• No results found

The Flyweight pattern could be considered an example of the crow's feet coming home to roost. This pattern "uses sharing to support large numbers of finegrained objects efficiently" [Gamma et al. 1995, p. 195].

The Problem

Translating this goal to the database context reduces it to eliminating redundancy by separating intrinsic and extrinsic data—in other words, normalization. Extrinsic data is data that varies with the context rather than with the object; intrinsic data varies with the object. In database terms, an OID (a primary key) identifies an object. Intrinsic data is data that, in the context of a set of objects, varies with the oid. In relational database terms, this translates to a functional dependency on the entire primary key, or fourth normal form (more on this in Chapter 11). Reducing the redundancy means creating objects with only intrinsic data and putting the extrinsic data in a separate place. The difference between the situation that leads to the Flyweight pattern and the concept of normalization is sharing. If the application uses a large number of objects, the duplication of intrinsic data among all the objects can be extreme, depending on the size of each object's intrinsic data. All this duplication can lead to a bloated database and a bloated application. By sharing the data, you can reduce the overall storage requirements a great deal—always an advantage in database (and application) design.

Consider a situation in the commonplace book encyclopedia system, for example. Many of the images in the database are composite images made up of different components. Many of these components are the same, just positioned and colored differently in each image. If you store the images as parent-child composites, with each component accompanied by its location and color, you store all the components of each composite image

separately. That means there is quite a lot of redundancy in the database. By sharing the components, you can greatly reduce the overall storage requirements. You can separate the images and store only the coloring and location information with the composite along with a pointer to the shared image.

This situation is common with computerization of real-world things. The first blush look at any situation yields millions of things; only on carefully analyzing the objects do you begin to see the similarities. In the extreme, you need theoretical science and its ability to break through irregularity with systematic innovation that reduces the chaos to a series of laws.

The Solution

The Flyweight pattern breaks apart the objects into intrinsic and extrinsic properties to enable the system to represent as few objects as possible by sharing. Figure 8-5 illustrates the abstract Flyweight pattern.

The Flyweight class is an abstract class that represents the needs of the subclasses for extrinsic information. The various bits of extrinsic information are all elements of the various interface operations. The FlyweightFactory class is the class that manages the collection of Flyweight objects, sharing them among various client objects. It has builder methods that either construct the shared object for the first time or just return it if it already exists. It may also have builder methods for Flyweight subclasses that just create the unshared objects every time you call the method. The FlyweightFactory also owns the transient aggregate data structure that holds the objects and the query code that reads them from the database. The ConcreteFlyweight class represents the actual intrinsic state of the Flyweight object shared between various classes.

Figure 8-6 shows the image example as a Flyweight pattern. This example simplifies the interface. The

ImageFactory produces either a shared image or an unshared composite image, depending on what its client wants. It stores the collection of shared images, querying them from the database as needed (or all at once if that makes sense). The interface in FlyweightImage imports context and location information as needed. The image bitmap (or whatever the Image data type is) is the intrinsic state in the shared concrete Flyweight class SharedImage. The other concrete class, CompositeImage, stores the set of children as a Composite pattern—no sharing.

In the database, a central table or object cluster stores the shared images. Another table or class, which Figure 8-6

does not show, stores the location and coloring information for the images, mapping these to the shared image through a foreign key or association of some kind. When the application needs a shared image, the client asks the Factory for it, supplying an Image OID identifier as the key. The factory manages the querying of previously created images or the creation of new ones.

Figure 8-6: The Image ComponentHierarchy as a Flyweight Pattern

Note To keep things simple, the example in Figure 8-6 does not include the operations to implement the Composite pattern, such as First and Next or Getlterator.

Discussion

Persistent Flyweights differ from your usual persistent objects mainly in the relationships they have with other objects. In a relational database, the number of links to the Flyweight should be high enough to justify keeping it normalized instead of denormalizing to reduce the overhead of joins in the application.

Much of the Flyweight pattern is about how to represent the objects in memory, not how to store them in the

database. In an OODBMS, for example, you have the option of making the Factory a persistent class and storing the links to shared Flyweights as persistent links in a set association. You then just retrieve the Factory and use the interface to retrieve the Flyweights into memory. With a relational database, this isn't really useful, since the Factory object has no state to store in a relational mapping. You can just as easily create the Factory and query the OIDs of the shared objects into an internal mapping structure, then query the objects as the clients request them through the Factory interface.

A critical fact of life with the Flyweight pattern is that all the subclasses must use the same types of extrinsic information. If you have any complexity in this regard (subclasses using different kinds of information) you need to extend the Flyweight with an aggregating pattern. That pattern lets you represent multiple kinds of interfaces in a single class hierarchy. In a Flyweight, data is either intrinsic or extrinsic, with all extrinsic data being passed through the Flyweight interface.

Sharing, as Chapter 7 points out, is a different kind of aggregation. When you share objects, you can no longer rely on the parent creating the object and managing it. Like any joint venture, the shared Flyweight needs a separate management to survive. That means a Factory, or some other manager object, that controls the set of Flyweights and implements the sharing capability of the set. For the most part, the Factory is a transient object in charge of creating and destroying Flyweight objects from persistent data. You can potentially make the Factory persistent if it contains data about the Flyweight, such as a special way to refer to the object (an indexing scheme for image elements based on strings, for example).

The original pattern [Gamma et al. 1995] states that the effectiveness of the pattern increases as the amount of object state that you can make extrinsic increases. Actually, the effectiveness increases as the amount of object state that you can make intrinsic increases. The more you can share, the better off you are.

If the extrinsic state is complex and requires additional normalization structuring, you may find that you are

approaching the number of objects you had in your original problem. This is the classic case for denormalization, the recombining of elements of a normalized database to reduce join complexity and increase performance.

Denormalization reduces the total number of objects (rows in a relational database) and the number of operations you must perform to navigate between objects. Consult Chapter 11 for a complete discussion of normal forms, normalization, and denormalization. If you find yourself creating a huge number of classes as you implement your Flyweight, you're almost certainly a candidate for flypapering the thing and denormalizing your database.

A relational database implements a Flyweight as a straightforward series of tables. The superclass Flyweight

(FlyweightImage in Figure 8-6) shows the abstract, extrinsic interface for the various image components. Since it has no state, the table contains only the primary key for the subclass tables. You could alternatively define the Flyweight class as a UML interface instead of inheriting from it as a superclass and eliminate it entirely from the relational database. This produces the classic normalization scenario. In both cases, the references are a classic referential integrity foreign key relationship.

In an ORDBMS, the Flyweight pattern uses types and references rather than table nesting. Table nesting is a classic example of a composite aggregation, where the parent table owns the nested table. Instead, you break out the Flyweight class from the extrinsic classes and refer to it from them. You must maintain referential integrity in your application or in triggers, though, as references don't have automatic referential integrity mechanisms (see Chapter 12 for details).

In an OODBMS, you create the Flyweight superclass and its subclasses and retrieve the extent of the Flyweight through that class or the individual extents through the subclasses. You can also make the Factory persistent and thus make the set of Flyweight objects a persistent association using an OODBMS. You thus access the Flyweights through the Factory, just as you would in a purely transient organization. You use bidirectional relationships with automatic referential integrity maintenance for all these associations (see Chapter 13 for details).