Knowledge Systems Corporation

About KSC

Press and Media

Job Opportunities

KSC Articles

Contact KSC

Site Map

Home


Courses
Smalltalk Courses
Java Courses
OO Courses
 
Opportunities
Jobs with Smalltalk
Jobs with Java
Jobs with .NET
Jobs with Data Base
Employers Sign Up Here
 
Business Services
Migration Services
System Implementation
Application Development
 
Education Services
Tutoring
Immersion Programs
 


 

The Type Object Pattern

by Ralph Johnson and Bobby Woolf

Intent

Decouple instances from their classes so that those classes can be implemented as instances of a class. Type Object allows new "classes" to be created dynamically at runtime, lets a system provide its own type-checking rules, and can lead to simpler, smaller systems.

Also Known As

Power Type [MO95], Item Descriptor [Coad93], Metaobject [KRB91], Data Normalization

Motivation

Sometimes a class requires not only an unknown number of instances, but an unknown number of subclasses as well. Although an object system can create new instances on demand, it usually cannot create new classes without recompilation. A design in which a class has an unknown number of subclasses can be converted to one in which the class has an unknown number of instances.

Consider a system for tracking the videotapes in a video rental store's inventory. The system will obviously require a class called "Videotape." Each instance of Videotape  will represent one of the videotapes in the store's inventory.

 

Mission Software

Has created a Smalltalk compiler for the Java Virtual Machine. This compiler allows Smalltalk to run on any JVM. The compiler currently produces 100% Java class files fully compatible with the Sun Java Virtual Machine specification. This allows Smalltalk and Java code to interact seamlessly and allows Smalltalk programs to run anywhere Java runs! Click to learn more

 

However, since many of the videotapes are very similar, the Videotape instances will contain a lot of redundant information. For example, all copies of Star Wars  have the same title, rental price, MPAA rating, and so forth. This information is different for The Terminator , but multiple copies of   The Terminator  also have identical data. Repeating this information for all copies of Star Wars  or all copies of The Terminator  is redundant.

One way to solve this problem is to create a subclass of Videotape  for each movie. Thus two of the subclasses would be   StarWarsTape  and   TerminatorTape . The class itself would keep the information for that movie. So the information common to all copies of Star Wars  would be stored only once. It might be hardcoded on the instance side of StarWarsTape  or stored in variables on the class side or in an object assigned to the class for this purpose. Now Videotape  would be an abstract class; the system would not create instances of it. Instead, when the store bought a new copy of The Terminator  videotape and started renting it, the system would create an instance of the class for that movie, an instance of TerminatorTape .

This solution works, but not very well. One problem is that if the store stocks lots of different movies, Videotape could require a huge number of subclasses. Another problem is what would happen when, with the system deployed, the store starts stocking a new movie-perhaps Independence Day . There is no   IndependenceDayTape  class in the system. If the developer did not predict this situation, he would have to modify the code to add a new IndependenceDayTape  class, recompile the system, and redeploy it. If the developer did predict this situation, he could provide a special subclass of   Videotape --such as UnknownTape --and the system would create an instance of it for all videotapes of the new movie. The problem with UnknownTape  is that it has the same lack of flexibility that Videotape  had. Just as Videotape  required subclasses, so will UnknownTape , so   UnknownTape  is not a very good solution.

Instead, since the number of types of videotapes is unknown, each type of videotape needs to be an instance of a class. However, each videotape needs to be an instance of a type of videotape. Class-based object languages give support for instances of classes, but they do not give support for instances of instances of classes. So to implement this solution in a typical class-based language, you need to implement two classes: one to represent a type of videotape ( Movie ) and one to represent a videotape ( Videotape ). Each instance of Videotape  would have a pointer to its corresponding instance of Movie .

Image

This class diagram illustrates how each instance of Videotape  has a corresponding instance of Movie . It shows how properties defined by the type of videotape are separated from those which differ for each particular videotape. In this case, the movie's title and how much it costs to rent are separated from whether the tape is rented and who is currently renting it.

Image

This instance diagram shows how there is an instance of Movie  to represent each type of videotape and an instance of Videotape  to represent each video the store stocks. Star Wars  and The Terminator  are movies; videotapes are the copy of Star Wars  that John is renting versus the one that Sue is renting. It also shows how each Videotape  knows what type it is because of its relationship to a particular instance of Movie .

If a new movie, such as  Independence Day , were to be rented to Jack, the system would create a new Movie  and a new Videotape  that points to the Movie . The movie is  Independence Day  and the tape is the copy of Independence Day  that Jack ends up renting.

Videotape ,   Movie , and the is-instance-of relationship between them (a Videotape  is an instance of a Movie ) is an example of the Type Object pattern. It is used to create instances of a set of classes when the number of classes is unknown. It allows an application to create new "classes" at runtime because the classes are really instances of a class. The application must then maintain the relationship between the real instances and their class-like instances.

The key to the Type Object pattern is two concrete classes, one whose instances represent the application's instances and another whose instances represent types of application instances. Each application instance has a pointer to its corresponding type.

Keys

A framework that incorporates the Type Object pattern has the following features:

 

The framework may also include these variations on the pattern:

 

Applicability

 

Structure

Image

The Type Object pattern has two concrete classes, one that represents objects and another that represents their types. Each object has a pointer to its corresponding type.

Image

For example, the system uses a TypeObject to represent each type in the system and an Object to represent each of the instances of those TypeObjects. Each Object has a pointer to its TypeObject.

 

Participants

 

TypeClass and Class are classes. TypeObject and Object are instances of their respective classes. As with any instance, a TypeObject or Object knows what its class is. In addition, an Object has a pointer to its TypeObject so that it knows what its TypeObject is. The Object uses its TypeObject to define its type behavior. When the Object receives requests that are type specific but not instance specific, it delegates those requests to its TypeObject. A TypeObject can also have pointers to all of its Objects.

Thus Movie  is a TypeClass and Videotape  is a Class. Instances of Movie  like   Star Wars , The Terminator , and   Independence Day  are TypeObjects. Instances of Videotape  like John's Star Wars  and Sue's Star Wars  are Objects. Since an Object has a pointer to its TypeObject, John's videotape and Sue's videotape have pointers to their corresponding Movie , which in this case is Star Wars  for both videotapes. That is how the videotapes know that they contain Star Wars  and not some other movie.

Collaborations

 

Consequences

The advantages of the Type Object pattern are:

 

 

The disadvantages of the Type Object pattern are:

 

Implementation

There are several issues that you must always address when implementing the Type Object pattern:

 

 

There are other issues you may need to consider when implementing the Type Object pattern:

 

 

The hard part of Type Object occurs after it has been used. There is an almost irresistible urge to make the TypeObjects more composable, and to build tools that let non-programmers specify new TypeObjects. These tools can get quite complex, and the structure of the TypeObjects can get quite complex. Avoid any complexity unless it brings a big payoff.

 

Sample Code

Video Store

Start with two classes, Movie  and Videotape .

    Object ()

        Movie (title rentalPrice rating)

        Videotape  (movie isRented renter)

 

Notice how the attributes are factored between the two classes. If there are several videotapes of the same movie, some can be rented while others are not. Various copies can certainly be rented to different people. Thus the attributes isRented  and renter  are assigned at the Videotape  level. On the other hand, if all of the videotapes in the group contain the same movie, they will all have the same name, will rent for the same price, and will have the same rating. Thus the attributes title , rentalPrice , and   rating  are assigned at the Movie  level. This is the general technique for factoring the TypeObject out of the Object: Divide the attributes that vary for each instance from those that are the same for a given type.

You create a new Movie  by specifying its  title . In turn, a Movie  knows how to create a new Videotape .

    Movie class>>title: aString

        ^self new initTitle: aString

 

    Movie>>initTitle: aString

        title := aString

 

    Movie>>new Videotape

        ^Videotape movie: self

 

    Videotape  class>>movie: aMovie

        ^self new initMovie: aMovie

 

    Videotape >>initMovie: aMovie

        movie := aMovie

 

Since Movie is Videotape 's TypeClass, Videotape  has a movie  attribute that contains a pointer to its corresponding Movie  instance. This is how a Videotape  knows what its Movie  is. The movie attribute is set when the Videotape  instance is created by Videotape  class>>movie: .

A Videotape  knows how to be rented. It knows whether it is already being rented. Although it does not know its price directly, it knows how to determine its price.

    Videotape>>rentTo: aCustomer

        self checkNotRented.

        aCustomer addRental: self.

        self makeRentedTo: aCustomer

 

    Videotape>>checkNotRented

        isRented ifTrue: [^self error]

 

    Customer>>addRental: aVideotape 

        rentals add: aVideotape.

        self chargeForRental: aVideotape rentalPrice

 

    Videotape>>rentalPrice

        ^self movie rentalPrice

 

    Videotape>>movie

        ^movie

 

    Movie>>rentalPrice

        ^rentalPrice

 

    Videotape>>makeRentedTo: aCustomer

        isRented := true.

        renter := aCustomer

 

Thus it chooses to implement its is Rented   behavior itself but delegates its   rentalPrice  behavior to its Type Object.

When Independence Day  is released on home video, the system creates a Movie  for it. It gathers the appropriate information about the new movie (title, rental price, rating, etc.) via a GUI and executes the necessary code. The system then creates the new   Videotape s  using the new Movie .

Video Store-Nested Type Objects

The Type Object pattern can be nested recursively. For example, many video stores have categories of movies-such as New Releases (high price), General Releases (standard price), Classics (low price), and Children's (very low price). If the store wanted to raise the price on all New Release rentals from $3.00 to $3.50, it would have to iterate through all of the New Release movies and raise their rental price. It would be easier to store the rental price for a New Release in one place and have all of the New Release movies reference that one place.

Thus the system needs a MovieCategory  class that has four instances. The MovieCategory  would store its rental price and each Movie  would delegate to its corresponding MovieCategory  to determine its price. Thus a MovieCategory  is the Type Object for a Movie , and a Movie  is the Type Object for a Videotape .

A MovieCategory  class requires refactoring Movie 's behavior.

    Object ()

        MovieCategory (name rentalPrice)

        Movie (category title rating)

        Videotape (movie isRented renter)

 

Before,   rentalPrice  was a attribute of Movie  because all videotapes  of the same movie had the same price. Now all movies in the same category will have the same price, so rentalPrice  becomes an attribute of MovieCategory . Since   Movie  now has a type object, it has an attribute- category -to point to its type object.

Now behavior like rentalPrice  gets delegated in two stages and implemented by the third.

    Videotape>>rentalPrice

        ^self movie rentalPrice

 

    Movie>>rentalPrice

        ^self category rentalPriceMovie

 

    Category>>rentalPrice

        ^rentalPrice

 

This example nests the Type Object pattern recursively where each MovieCategory  has Movie  instances and each Movie  has Videotape  instances. The system still works primarily with Videotape s, but they delegate their type behavior to Movie s, which in turn delegate their type behavior to MovieCategory s. Videotape  hides from the rest of the system where each set of behavior is implemented. Each piece of information about a tape is stored in just one place, not duplicated by various tapes. The system can easily add new MovieCategory s, Movie s , and   Videotape s  when necessary by creating new instances.

Video Store-Dynamic Type Change

Once  Independence Day  is no longer a New Release, its category can easily be changed to a General Release because its category is a Type Object and not its class.

    Movie>>changeCategoryTo: aMovieCategory

        self category removeMovie: self.

        self category: aMovieCategory.

        self category addMovie: self.

 

With the Type Object pattern, an Object can easily change its Type Object when desired.

Video Store-Independent Subclassing

The system could also support videodisks. The commonalities of videotapes and videodisks are captured in the abstract superclass RentableItem , where Videotape  and Videodisk  are subclasses. Both concrete classes delegate their type behavior to Movie , so  Movie  does not need to be subclassed.

    Object ()

        MovieCategory (name rentalPrice)

        Movie (category title rating)

        RentableItem (movie isRented renter)

            Videotape (isRewound)

            Videodisk (numberOfDisks)

 

Most of Videotape 's behavior and implementation is moved to   RentableItem . Now Videodisk  inherits this code for free.

Movie  may turn out to be a specific example of a more general Title  class. Title  might have subclasses like Movie , Documentary , and HowTo . Movies have ratings whereas documentary and how-to videos often do not. How-to videos often come in a series or collection that is rented all at once whereas movies and documentaries do not. Thus Title  might also need a Composite [GHJV95, page 163] subclass such as HowToSeries . Movie itself might also have subclasses like RatedMovie  for those movies that have MPAA ratings and UnratedMovie  for movies that don't.

    Object ()

        MovieCategory (name rentalPrice)

        Title (category title)

            Documentary ()

            HowTo ()

            Movie ()

                RatedMovie (rating)

                UnratedMovie ()

            TitleComposite (titles)

                HowToSeries ()

        RentableItem (title isRented renter)

            Videotape (isRewound)

            Videodisk (numberOfDisks)

 

The code above and the diagram below show the final set of classes in this framework.

Image

Movie  and Title  can be subclassed without affecting the way RentableItem  and Videotape  are subclassed. This ability to independently subclass Title  and RentableItem  would be impossible to achieve if the videotape  object had not first been divided into Movie  and Videotape  components. Obviously, all of this nesting and subclassing can get complex, but it shows the flexibility the Type Object pattern can give you-flexibility that would be impossible without the pattern.

Manufacturing

Consider a factory with many different machines manufacturing many different products. Every order has to specify the kinds of products it requires. Each kind of product has a list of parts and a list of the kinds of machines needed to make it. One approach is to make a class hierarchy for the kinds of machines and the kinds of products. But this means that adding a new kind of machine or product requires programming, since you have to define a new class. Moreover, the main difference between different products is how they are made. You can probably specify a new kind of product just by specifying its parts and the sequence of machine tools that is needed to make it.

It is better to make objects that represent "kind of product" and "kind of machine." They are both examples of type objects. Thus, there will be classes such as Machine , Product , MachineType , and ProductType . A ProductType  has a "manufacturing plan" which knows the MachineType s  that make it. But a particular instance of Product  was made on a particular set of Machine s. This lets you identify which machine is at fault when a product is defective.

Suppose we want to schedule orders for the factory. When an order comes in, the system will figure out the earliest that it can fill the order. Each order knows what kind of product it is going to produce. For simplicity, we'll assume each order consists of one kind of product. We'll also assume that each kind of product is made on one kind of machine. But that product is probably made up of other products, which will probably require many other machines. Thus, Product is an example of the Composite pattern [GHJV95, page 163] (not shown below). For example, a hammer consists of a handle and a head, which are combined at an assembly station. The wooden handle is carved at one machine, and the head is cast at another. ProductType  and Order  are also composites, but are not shown.

 

Image

There are six main classes:

    Object

We will omit all the accessing methods, since they are similar to those in the video store example. Instead, we will focus on how a factory schedules an order.

A factory acts as a Facade [GHJV95, page 185], creating the order and then scheduling it.

    Factory>>orderProduct: aType by: aDate for: aCustomer

        | order |

        order := Order product: aType by: aDate for: aCustomer.

        order scheduleFor: self.

        ^order

 

    Order>>scheduleFor: aFactory

        | partDate earliestDate |

        partDate := dueDate minusDays: productType duration.

        parts := productType parts collect: [:eachType |

            aFactory

                orderProduct: eachType

                by: partDate

                for: order].

        productType

            schedule: self

            between: self datePartsAreReady

            and: dueDate

 

    ProductType>>schedule: anOrder between: startDate and: dueDate

        (startDate plusDays: duration) > dueDate

            ifTrue: [anOrder fixSchedule].

        manufacturingMachine

            schedule: anOrder

            between: startDate

            and: dueDate

 

There are at least two different subclasses of ProductType , one for machines that can only be used to make one product at a time, and one for assembly lines and other machines that can be pipelined and so make several products at a time. A non-pipelined machine type is scheduled by finding a machine with a schedule with enough free time open between the startDate  and the dueDate .

    NonpipelinedMachineType>>schedule: anOrder between: startDate and: dueDate

        machines do: [:each | | theDate |

        theDate := each schedule

                            slotOfSize: anOrder duration

                            freeBetween: startDate

                            and: dueDate.

        theDate notNil ifTrue:

            [^each schedule: anOrder at: theDate]].

        anOrder fixSchedule

 

A pipelined machine type is scheduled by finding a machine with an open slot between the startDate and the dueDate.

    PipelinedMachineType>>schedule: anOrder between: startDate and: dueDate

        machines do: [:each | | theDate |

        theDate := each schedule

                            slotOfSize: 1

                            freeBetween: startDate

                            and: dueDate.

        theDate notNil ifTrue:

            [^each schedule: anOrder at: theDate]].

        anOrder fixSchedule

 

This design lets you define new ProductType s without programming. This lets product managers, who usually aren't programmers, specify a new product type. It will be possible to design a tool that product managers can use to define a new product type by specifying the manufacturing plan, defining the labor and raw materials needed, determining the price of the final product, and so on. As long as a new kind of product can be defined without subclassing Product, it will be possible for product managers to do their work without depending on programmers.

There are constraints between types. For example, the sequence of actual MachineTool s  that manufactured a Product must match the MachineToolType s  in the manufacturing plan of its ProductType . This is a form of type checking, but it can be done only at runtime. It might not be necessary to check that the types match when the sequence of   MachineTool s  is assigned to a Product, because this sequence will be built by iterating over a manufacturing plan to find the available MachineTool s . However, scheduling can be complex and errors are likely, so it is probably a good idea to double-check that a Product's sequence of MachineTool s  matches what its ProductType  says it should be.

Known Uses

Coad

Coad's Item Description pattern is the Type Object pattern except that he only emphasized the fact that a Type holds values that all its Instances have in common. He used an "aircraft description" object as an example. [Coad92]

Hay

Hay uses Type Object in many of his data modeling patterns, and discusses it as a modeling principle, but doesn't call it a separate pattern. He uses it to define types for activities, products, assets (a supertype of product), incidents, accounts, tests, documents, and sections of a Material Safety Data Sheet. [Hay96]

Fowler

Fowler talks about the separate Object Type and Object worlds, and calls these the "knowledge level" and the "operational level." He uses Type Object to define types for organizational units, accountability relationships, parties involved in relationships, contracts, the terms for contracts, and measurements, as well as many of the things that Hay discussed. [Fowler97]

Odell

Odell's Power Type pattern is the Type Object pattern plus the ability for subtypes (implemented as subclasses) to have different behavior. He illustrates it with the example of tree species and tree. A tree species describes a type of tree such as American elm, sugar maple, apricot, or saguaro. A tree represents a particular tree in my front yard or the one in your back yard. Each tree has a corresponding tree species that describes what kind of tree it is. [MO95]

Sample Types and Samples

The Type Object pattern has been used in the medical field to model medical samples. A sample has four independent properties:

 

This is easily modeled as a   Sample  object with four attributes: system, subsystem, collection procedure, and additive. Although the system (the person who gave the sample) is different for almost all samples, the triplet (subsystem, collection procedure, and additive) is shared by a lot of samples. For example, medical technicians refer to a "blood" sample, meaning a blood/aspiration/EDTA sample. Thus the triplet attributes can be gathered into a single SampleType  object.

A SampleType  is responsible for creating new Sample  objects. There are about 5,000 different triplet combinations possible, but most of them don't make any sense, so the system just provides the most common SampleType s. If another SampleType  is needed, the users can create a new one by specifying its subsystem, collection procedure, and additive. While the system tracks tens of thousands of   Sample s, it only needs to track about one-hundred SampleType s. So the SampleType s are TypeObjects and the Sample s are their Objects. [DeKezel96]

Signals and Exceptions

The Type Object pattern is more common in domain frameworks than vendor frameworks, but one vendor example is the Signal/Exception framework in VisualWorks Smalltalk. When Smalltalk code encounters an error, it can raise an Exception . The Exception  records the context of where the error occurred for debugging purposes. Yet the Exception  itself doesn't know what went wrong, just where. It delegates the what information to a Signal . Each Signal  describes a potential type of problem such as user-interrupt, message-not-understood, and subscript-out-of-bounds. Thus two message-not-understood errors create two separate Exception  instances that point to the same Signal  instance. Signal  is the TypeClass and Exception  is the Class. [VW95]

Reflection

Type Object is present in most reflective systems, where a type object is often called a metaobject. The class/instance separation in Smalltalk is an example of the Type Object pattern. Programmers can manipulate classes directly, adding methods, changing the class hierarchy, and creating new classes. By far the most common use of a class is to make instances, but the other uses are part of the culture and often discussed, even if not often used. [KRB91]

Reflection has a well-deserved reputation for being hard to understand. Type Object pattern shows that it does not have to be difficult, and can be an easy entrance into the more complex world of reflective programming.

Related Patterns

Type Object vs. Strategy and State

The Type Object pattern is similar to the Strategy and State patterns [GHJV95, page 315 and page 305]. All three patterns break an object into pieces and the c real objectî delegates to the new object-either the Type Object, the Strategy, or the State. Strategy and State are usually pure behavior, while a Type Object often holds a lot of shared state. States change frequently, while Type Objects rarely change. State solves the problem of an object needing to change class, whereas Type Object solves the problem of needing an unlimited number of classes. A Strategy usually has one main responsibility, while a Type Object usually has many responsibilities. So, the patterns are not exactly the same, even though their object diagrams are similar.

Type Object and Reflective Architecture

Any system with a Type Object is well on its way to having a Reflective Architecture [BMRSS96]. Often a Type Object holds Strategies for its instances. This is a good way to define behavior in a type.

Type Object vs. Bridge

A Type Object implementation can become complex enough that there are Class and Type Class hierarchies. These hierarchies look a lot like the Abstraction and Implementor hierarchies in the Bridge pattern [GHJV95, page 151], where Class is the abstraction and Type Class is the implementation. However, clients can collaborate directly with the Type Objects, an interaction that usually doesn't occur with Concrete Implementors.

Type Object vs. Decorator

An Object can seem to be a Decorator [GHJV95, page 175] for its Type Object. An Object and its Type Object have similar interfaces and the Object chooses which messages to forward to its Type Object and which ones to enhance. However, a Decorator does not behave like an instance of its Component.

Type Object vs. Flyweight

The Type Objects can seem like Flyweights [GHJV95, page 195] to their Objects. However, Type Object does not involve a Flyweight Factory that provides access to a Flyweight Pool. Nevertheless, two Objects using the same Type Object might think that they each have their own copy, but instead are sharing the same one. Thus it is important that neither Object change the intrinsic state of the Type Object.

Type Object and Prototype

Another way to make one object act like the type of another is with the Prototype pattern [GHJV95, page 117], when each object keeps track of its prototype and delegates requests to it that it does not know how to handle.

References

[BMRSS96] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, and Michael Stal. Pattern-Oriented Software Architecture - A System of Patterns . Wiley and Sons Ltd., 1996.

[Coad92] Peter Coad. "Object-oriented Patterns," Communications of the ACM . 35(9):152-159, September 1992.

[Fowler97] Martin Fowler. Analysis Patterns: Reusable Object Models . Addison-Wesley, Reading, MA, 1997.

[DeKezel96] Raoul De Kezel. E-mail correspondence.

[GHJV95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.  Addison-Wesley, Reading, MA, 1995; .

[Hay96] David Hay. Data Modeling Patterns . Dorsett House Publishing, 1996.

[KRB91] Gregor Kiczales, Jim des Rivieres, and Daniel Bobrow. The Art of the Metaobject Protocol . The MIT Press, Cambridge, Massachusetts, 1991.

[MO95] James Martin and James Odell. Object Oriented Methods: A Foundation . Prentice Hall, Englewood Cliffs, NJ, 1995.

[VW95] VisualWorks Release 2.5 , ParcPlace-Digitalk, Inc., Sunnyvale, CA, 1995;

 

 
Copyright © 1997 Ralph Johnson, Bobby Woolf, and Knowledge Systems Corporation. All Rights Reserved.

Ralph Johnson can be reached at Dept. of Computer Science, 1304 W. Springfield Ave., Urbana, IL 61801; .

Bobby Woolf can be reached at SilverMark.


 


Mission Software

DotNetBuzz

 

Knowledge Systems Corporation is a member of the Smalltalk Webring.

 This Smalltalk Webring site is owned by Knowledge Systems Corporation.
[ Previous Page | Next Page | Skip Next | List Next 5 | Random Link ]
Want to join the ring? Click here for info

Email:  Sales sales@ksc.com
Copyright © 2002 - Knowledge Systems Corporation