Previous article

Next article


Better Construction with Factories

Tal Cohen and Joseph (Yossi) Gil, Department of Computer Science, Technion—Israel Institute of Technology Technion City, Haifa 32000, Israel

space REFEREED
ARTICLE


PDF Icon
PDF Version

"The Factory-Owning Class Controls the Means of Production."
K. Marx [14]

Abstract

The polymorphic nature of object oriented programs means that client code expecting an instance of class C may use instead an instance of a class C' inheriting from C. But, in order to use such a different instance, one must create it, and in order to do so in current languages, must be familiar with the name of creating class. To break this coupling, we propose the novel notion of factories, which are class services (alongside methods and constructors) that manage the instancecreation step of object construction. In making the case for factories we propose a five-dimensional framework for understanding and analyzing the class notion in various programming languages.

We show that factories can naturally replace the "creational" design patterns, and describe the design and implementation of a JAVA language extension supporting both supplier-side and client-side factories. Possible implementations in other languages are discussed as well.


1 INTRODUCTION

Good programming languages support, at the language level, the general principle of hiding implementation details from the client [19]. Indeed, most contemporary object oriented programming languages let, sometime even force, the programmer to hide the implementation details of methods that a class offers. An inspiring case in point is Meyer’s principle of uniform access [15, p.57], stating that

"All services offered by a module [i.e., a class] should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation."

This paper starts from the observation that despite the progress in language design, there is still a family of services which reveal more than they should of their implementation secrets. These services are what is known as creation procedures in some languages and constructors in others. Constructors are distinguished from the other services that a classmay offer in that the client cannot apply them to a polymorphic object; instead the client is responsible for creating such an object, and therefore must know the precise name of the class that creates it.

The polymorphic nature of classes is advertised as means for separating interface from implementation. Object oriented polymorphism means that a client may use instances of different subclasses to implement the same protocol. But, the trouble is that in order to be able to use such instances, one needs to create them somewhere, and the creation process is coupled with the name of the creating class. Breaking this coupling seems to be an intriguing chicken and egg riddle: Interface (or protocol) can be separated from implementation, but in order to select a particular implementation of a given protocol one must be familiar with at least one of these implementations.

Our solution to this cyclic dilemma is by making the selection of an implementation part of the interface. In the object-oriented terminology, this means that we allow a class to offer a set of services, what we call factories, for generating instances of its various subclasses. Factories are first-class class members (alongside methods and constructors), but, unlike constructors, factories encapsulate instance management decisions without affecting the class’s clients. Our contribution includes also a re-implementation of the Java compiler that supports factories; this implementation requires no changes to the JVM.

Factories directly attack the change advertising problem: Suppose that the implementation of a class (indeed, the internals of any software unit) is changed or specialized, but, as is the case with inheritance or dynamic aspects, that the original version still remains. Then, the fact that there was a change must be advertised to the clients that wish to enjoy its benefits. Specifically, an instance of a class C' inheriting from C can be used anywhere an instance of C is used; but clients must be aware that C' exists, and be familiar with its name and its particular repertoire of constructors, in order to create such instances.

Existing solutions to the change advertising dilemma can be found in several popular frameworks, which act outside of the programming language. This includes, for example, the J2EE [20] mechanism for obtaining instances of Enterprise JavaBeans (EJBs, [6]). Clients must not directly invoke constructors for EJBs; rather, special methods of "home objects" must be used, effectively encapsulating the creation process and providing the platform with the ability to decide an instance of which (sub)class will be generated.

Likewise, users of the Spring Application Framework1 should only obtain instances (of any class) by using special "bean factory" objects. The need for factories is further evident from the popularity and usefulness of design patterns that strive to emulate their functionality, including Abstract, Factory, Factory Method, Singleton [10], and Object Pool [12]. However, both the frameworks and the design patterns introduce certain restrictions that the developers must adhere to (such as never invoking constructors directly). Just like these design patterns, factories are not compelled to return a new class instance. In not betraying the secret whether a new instance was generated or an existing one was fetched, they can be thought as applying the principle of uniform notation to

instantiation. Much as with uniform access for "features" (attributes or functions) in Eiffel, factories prevent upheaval in client classes whenever an internal implementation decision of the class is changed.

More concretely, we describe the design and implementation of an extension to the Java programming language to support factories. In this extension, factories act as methods that overload the new operator. But, unlike new overloading in C++, factories are not concerned with memory allocation but rather with instance creation and specific subclass selection decisions. We offer two varieties of factories:

  • Client-side factories help localize instantiation statements, whereby a reimplementation can be selectively injected to certain clients.
  • Supplier-side factories provide classes with fine control over their instantiation, and help in a global advertising of a change in the implementation.

Factories enable the encapsulated implementation of the "creational" design patterns listed above, either for all clients (using supplier-side factories) or for specific ones (using client-side factories). They provide a language-level solution to the change advertising dilemma, without presenting developers with any restrictions or complications.

Outline Sec. 2 starts by setting forth a common terminology for the discussion, and tries to unify some of the different perspectives offered in the literature to the class concept. Using this terminology, Sections 3 and 4 expand on the motivation, by highlighting certain limitations of constructors. Factories are the subject of Sec. 5, which describes their Java syntax and some of the applications. This section also shows how factories support many classical design patterns. Sec. 6 describes how coupling between classes can be decreased using factories, and Sec. 7 describes the notion of client-side factories. Finally, Sec. 8 discusses the extension of the factories idea to other programming languages and concludes.

2 TERMINOLOGY

There are many ways in which people perceive the notion of class: as a "repository for behavior associated with an object" [2, p.13], a "unit of software decomposition" and a "type" [15, pp.170–171], a "tool for creating new types" [21, p.223], a "group [of objects]" [13, p.50]2, a "set of objects that share a common structure and a common behavior" [1, p.93], etc. This section tries to unify these perspectives and propose a terminology (a conceptual framework if you will) for comparing and understanding the notion of a class in different programming languages.

We distinguish five, not entirely orthogonal, dimensions of class analysis: commonality, encapsulation, morphability, binding, and purpose. The most interesting dimensionis purpose, by which we identify, for each syntactical element of a class, a programminglanguage purpose. In Sec. 3 we shall argue that, judged by these dimension of evaluation, constructors make a bit of weird bird. Let us now describe in greater detail each of the five dimensions in turn.

1. Commonality. This dimension makes the distinction between common elements of the class notion (e.g., class variables and methods in SmallTalk) and particular such elements (e.g., instance variables and methods). More precisely, an element is common if its incarnation in different instances of the class is identical; otherwise, it is particular. Thus, particular elements may be used only in association with a specific class instance. Also, common elements cannot access particular elements.

2. Encapsulation. (Also known as Visibility.) A class may encapsulate (i.e., set the visibility of) its elements. C++’s three visibility levels, just as Java's four, are orthogonal to commonality.

3. Morphability. Morphability indicates the class element’s ability to obtain a shape, or be re-shaped, in a subclass. In other words, morphability pertains to the kind of changes that a subclass may apply to components of the base class in the course of inheritance.

There is a great variety in the morphability capabilities in different programming languages. For example, C++ allows a subclass to decrease the visibility of inherited members, Oberon [22] forbids overriding, Java sports final members and allows data members to be hidden [11, Sect. 8.3.3], while Eiffel allows re-implementation of a data member as a method, and method renaming. The analysis of this variety in full is beyond the scope of this paper.

4. Binding. As the name suggest, in this dimension we make the distinction between statically-bound and dynamically-bound elements. Of course, this distinction can be made only for class elements which can be replaced or altered in a subclass. Non-virtual methods in C++ are famous for being statically bound.

Observe that in most languages, commonality and binding are not orthogonal. Specifically, we find that common elements are often statically bound. The linkage between static binding and commonality is so entrenched that common methods and fields in languages such as Java, C# and C++ are marked with the static keyword.

The phenomena can be explained by the reliance of dynamic binding on dispatching information associated with individual objects. Common elements are statically bound since they may exist even when there are no instances to the class.

5. Purpose. Classes, being a unit of software decomposition, can be subjected to Parnas’s [19] classical distinction between the interface and materialization (which Parnas calls "implementation") perspectives of a software component. We say that the interface and materialization are purposes that the class serves as a whole, and characterize its elements by this purpose.

But, unlike the software components of the seventies, classes are instantiable. Accordingly, we break the interface of a class into two facets: the forge and the type. Similarly, we distinguish between three facets in the materialization: the implementation of the type, the mill behind the forge, and the mold into which instances are cast.

More specifically, the forge of the class is the collection of operations that can be used to create objects; the type is the set of messages that these instances may receive, along with their visibility specification; and, the implementation is the body of the methods executed in response to these messages. There is a subtle distinction between the mill and the mold, which together realize the class’s forge: The mold is the memory layout which instances of this class follow. It consists solely of field definitions. The mill is the set of constructor bodies.

To understand these terms better, consider class Vector from the standard Java library. The forge of Vector, depicted in Fig. 2.1(a), includes the signature of the four

Figure 2.1: The forge, type and mold of java.lang.Vector.

constructors provided by the class: the default constructor Vector(), the copy constructor Vector(Collection), a variant that specifies the initial capacity (Vector(int)), and a variant that specifies both the initial capacity and the capacity growth increment (Vector(int, int)).

Fig. 2.1(c) shows the type of Vector, methods such as addElement, capacity, and others, as well as fields such as capacityIncrement and elementData. Superclasses also add to the type; in this case, the type of Vector includes methods and fields inherited from three superclasses. Each superclass and superinterface also adds an upcast operator.

We see that the type includes the signature of all non-private fields and methods of the class. Thus what we call type here is in fact the class’s structural type, to which Java applies a name, making it a nominal type.

The type does not include details such as a specification of the order by which methods may be invoked, pre- and post-conditions, or other classes with which the class may interact while implementing each method. These may be thought of as the class protocol.

The mold for creating new objects is defined by the collection of all fields in this class and all of its supertypes. Specific languages or language implementations can include hidden fields in the mold, such as run-time type information, the Virtual Method Table [8] used in C++, etc. Fig. 2.1(b) presents the mold defined by class Vector. It includes fields defined in Vector as well as any fields inherited from superclasses, along with any hidden field added by the JVM.

Finally, the implementation is the body of the methods defined by the class or any of its superclasses, while the mill is the body of the constructors defined in this class.

3 CONSTRUCTOR ANOMALIES

Factories, the language extension proposed in this paper, are methods which return new class instances. Syntactically, a factory is a method which overloads the new operator with respect to a certain class.

In the terminology of the previous section, the signature of a factory belongs in the forge, while its body belongs in the mill. In this respect, factories are similar to constructors in mainstream object-oriented languages, the means by which a class’ clients may obtain instances.

In analyzing constructors (in, e.g., Java or C++) with this terminology, we find that exhibit three fundamental anomalies, which underline the need for the alternative approach that factories offer:

1. Commonality. In Java, the syntax for creating an instance of class MyClass is new MyClass(), i.e., it refers to the class name. In contrast, in EIFFEL the syntax is !!myInstance, i.e., referring to a variable. The difference between the languages is not a coincidence. Constructors are anomalous in that they are simultaneously common and particular: common—since they are invocable without an instance; particular—since they work on an object.

This anomaly raises the dilemma of method binding inside constructor bodies. Method invocation from the mill follows a static binding scheme in C++3; in JAVA and C#, however, dynamic binding is used. Neither approach is without fault. Static binding can lead to illegal invocation of pure virtual methods. Dynamic binding prevents methods, invoked from within the mill, from assuming that all fields were properly initialized. Dynamic method binding in constructors leads, among other things, to difficulties in implementing non-nullable types, as described by Fähndrich and Leino [9]: during construction, fields of non-null types may contain null values.

2. Morphability. In examining the morphability of the five facets of a class purpose, we find that changes to four out of these are not arbitrary: The type definition of a subclass is an extension of the type definition of the superclass. Similarly, the mold of a subclass is an extension of the mold of the superclass. Also, the implementation can either replace or extend the implementation in the superclass, and the mill (constructor body of a subclass) must extend (i.e., invoke) the mill of the superclass.

In contrast, the forge of the subclass is independent of the forge of the superclass— the forge cannot be extended: it is not even inherited, and each class must define its own set of constructor signatures anew. The second constructor anomaly lies in the fact that the the construction protocol is not inherited, yet, each constructor body must invoke a constructor of the base class.

3. Binding. Third, it is mundane to see that a call to a constructor obeys a static binding scheme, and it takes just a bit of pondering to understand the difficulties that this scheme brings about. If a class C' inherits from C, then C' should be always substitutable for C. An annoying exception is made by constructor invocation sites in client code; these have to be manually fixed in switching from C to C'.

The Gang of Four [10, p.24] place this predicament first in their list of causes for redesign, saying: " Specifying a class name when you create an object commits you to a particular implementation instead of a particular interface".

Interestingly, in Eiffel, although it has a strict dynamic binding policy, and although creation methods can be overridden, and although creation syntax is similar to method invocation, it is still the case that creation instructions such as !!x.make are statically bound.

4 STAGES OF OBJECT CREATION

Fig. 4.1 demonstrates another issue with constructors. The figure depicts abstract class Baby whose constructor announces the baby’s birth, and concrete class NamedBaby inheriting from it. Method announce is refined in NamedBaby, extending the announcement with details about the newborn’s gender and name.

A client who has new baby boy named "John", may then write

NamedBaby myBoy = new NamedBaby("John", true);

and be surprised by the printout "New baby: Her name is null", which is explained by the announcement being made before the subclass’s fields are initialized. This lack of crisp separation between field initialization and the rest of the construction code, can even result in runtime exceptions, e.g., if NamedBaby.announce invokes name.length().

C++ is not much better: The C++ equivalent of Fig. 4.1 would print partial (albeit more sensible) output, "New baby:". Also, C++ would produce a runtime error if announce() is made abstract in class Baby.

This example motivates our distinction between three conceptual steps in an instance’s birth process (later we shall argue that the separation between these is better served by factories): (a) Creation, in which the object’s actual type is selected, memory is allocated and structured by the mold; (b) Initialization, in which fields are set to their initial values; and (c) Setup, in which the mill is executed.

Figure 4.1: Interwoven initialization and setup in JAVA constructors.

These three steps correspond exactly to steps C1, C2 and C4 in the effects of a creation instruction !!x in Eiffel [15, p.237]. The missing step, C3, is the attachment of the newly created object to the reference variable x; however, in languages such as JAVA and C++ the invocation of a constructor is an expression rather than a statement, and can be performed without assigning the result to a variable. (EIFFEL also supports the invocation of a creation procedure as an expression [7, Sec. 8.20.18], in which case step C3 is absent.)

The initialization step is realized in C++ by what is called the initialization list (written just after the constructor’s signature). In JAVA and C# it is expressed using initializer values (or defaults) for fields, whereas the instance initializer block and the constructor bodies perform the setup. In Eiffel, it is the assignment of standard default values to fields. As the example shows, however, initialization with default values is insufficient. Developers should be able to initialize all fields, across all levels of inheritance (i.e., complete step (b)) before setup code is being executed (step (c), the announcement in our example); initialization and setup should be unwoven. We further note that none of these languages provides the developer with control over the creation step.

Note that overloading the new operator in C++ grants us control over memory allocation, but not over the kind of object to be created, nor the decision if a new object has to be created at all.

We argue that good design of elaborate software systems often requires intervention in the creation step. Indeed, there are a number of successful design patterns, including Abstract Factory, Factory Method, Singleton, and Object Pool,which address precisely this need. The control that these "creational patterns" grant the programmer over the creation step is achieved by replacing constructor signatures from the forge facet with a different, statically-bound, common method (e.g., getInstance)4.

Unfortunately, in contrast with most other patterns, the creational patterns cannot be implemented in OO languages without revealing implementation details to the client: If class T is implemented as a SINGLETON, then clients of this class cannot write new T() and expect the correct instance to be returned; rather, they must be aware of the nonstandard creation mechanism, in violation of the uniform access principle. As a result, if a class evolves during development so that the new version employs (e.g.) an instance pool, all clients must be updated to use the getInstance method rather than the constructors; the use of creational patterns cannot be encapsulated as part of the class implementation.

Creational patterns often collide with inheritance. To enforce the use of a get-Instance method and prevent accidental direct access to the constructors, all constructors can be made private, with the undesired implication that the class cannot be subclassed. The alternative of defining the constructor as protected, is problematic in Java, since such constructors are visible to all classes in the same package.

Worse still, since method getInstance must be shared, it cannot be overridden in subclasses: If C' is a subclass of C, then the expression C'.getInstance() is valid—but returns an instance of C! This happens because getInstance is technically part of the type, while conceptually being part of the forge.

We shall see that factories enable a clear-cut separation between creation and initialization and setup, and allow for proper encapsulation of the creation step.

5 FACTORIES

Class STemplate in Fig. 5.1 shows how the SINGLETON design pattern can be realized by overriding new with the factory defined in lines 4–7. This factory is invoked whenever the

Figure 5.1: A Singleton defined using a factory.

expression new STemplate() is evaluated, in class STemplate or any of its clients. Note that the factory is declared static, which stresses that it binds statically, and that (unlike constructors) it has no implicit this parameter. Examining the factory body we see that it always returns the same instance of the class. Thus, clients need not be explicitly aware of STemplate being a singleton, and will not be affected if this implementation decision

is changed. (In the specific case of the SINGLETON design pattern, clients can compare instances to realize that only one exists. Other patterns, such as Instance Pool, can be completely invisible to clients.)

A factory must either return a valid object of the class, or throw an exception. (Should the factory’s return value be null, a NullPointerException results.)

Suppose that C' is a subclass of C. Then, a factory of C can return an instance of C'. This can be done by invoking any method which returns an instance of C0, including a factory of C'—e.g., by a statement such as return new C'(· · · ). If the factory however chooses to create an instance of class C, then it should invoke the constructor; yet writing new C(· · · ) (e.g., new STemplate() in the example) would recurse infinitely. Instead, the factory invokes the class constructor directly with the expression this(· · · ) (line 5 in the example).

We chose to overload the keyword this, particularly, its use for invoking a constructor. No ambiguity arises: In constructors, the function call this(· · · ) occurring in the first line can substitute the mandated call to super with a call to a different constructor in the same class (as in standard JAVA). Such a call does not create an instance, nor does it return a value, and it must appear only as the very first step in the constructor body.

In a factory, this(· · · ) stands for a call to a constructor of the class. The call creates a new instance and returns a value; it may occur multiple times (or not at all), and in any location inside the factory body. The factory can choose to return the value generated by such a call. (In the case of the STemplate class, the value is cached to a static field, which is then returned.)

The constructor can only be called from a factory in the same class; any use of new C(· · · ), either from outside class C or from inside it, will invoke a factory rather than a constructor.

While there are many different solutions to the specific issue of singletons, (e.g., declaring an object—rather than a class—in SCALA [18], or using prototype-based languages, such as CECIL [5]), the factory solution is not specific to singletons, and can be used for any creational design pattern. More examples will be presented in the sequel.

As usual with overloading, a factory may have parameters, which are matched against the actual parameters in the creation expression. A parameterized factory could be used for, e.g., implementing the FLYWEIGHT pattern: To do so, the factory returns, if possible, an existing object from its pool, and only creates an instance if no such object exists.

Like constructors, factories are not inherited. Had class C' inherited a factory new() from its superclass C, then the expression new C'() might yield an instance of C, contrary to common sense. Thus, the problem of C'.getInstance() yielding an instance of C, described in Sec. 3, does not occur with factories.

In contrast, when factories are employed, the expression new C() can yield an instance of C', since a subclass is always substitutable for its superclass.

Factories also allow developers to separate the initialization and setup stages of object construction. The mixup of Fig. 4.1 is resolved by the factory based implementation in Fig. 5.2, in which the call new NamedBaby("John",true) yields the expected "New baby: His name is John" output.

Figure 5.2: Re-implementation of Fig. 4.1 with factories

The implementation in the figure adheres to the simple rule that field are initialized in constructors, and other setup is carried out by the factory. In particular the announcement of the birth is made in the factory of NamedBaby (line 20).

Automatically Generated Factories

A definition of a factory with a certain signature hides the constructor with the same signature. Such hidden constructors can only be invoked from the factory of a class, regardless of their access level. Let us now deal with the dual situation, i.e., a constructor without a factory. Backward compatibility of our extension is achieved by the following perspective: An expression of the form new S(· · · ) is always implemented by a factory whose signature matches the actual parameters. This can be either a user-defined factory, or an automatically generated factory (AGFa). The automatic generation of factories is governed by:

Fig. 5.3 shows an example of the AGFa rule. The class defined in Fig. 5.3(a) has a factory with no parameters. It also has a two-parameters constructor, with no matching factory. Fig. 5.3(b) shows the AGFa that the compiler (internally) injects into the class.

Figure 5.3: A class (a) with a constructor and its AGFa (b).

Recall that in plain JAVA, instances of abstract classes cannot be created, even though such classes have constructors. The following argument uses the AGFa rule to explain this: Instances can only be created by a new expression, which must have a matching factory. However, by the AGFa rule, abstract classes in plain JAVA do not have factories.

Conversely, if an abstract class Sa does define factories, then you can write new Sa(· · · ) in your code. Fig. 5.4 shows an abstract class, ScrollBar, with a factory. This example is modelled after the famous example [10, p.87] of the Abstract Factory design pattern. The code in the figure improves on the original implementation of the design pattern, in that the client is not aware that an abstract factory stands behind the scenes of the simple call new ScrollBar(). (As we shall see later, the internal implementation of the widget factory class itself can also be improved with factories.)

Figure 5.4: An abstract class with a factory.

As shown in Fig. 5.5, interfaces may also have factories. The figure shows an interface, DirectoryEntry, whose factory makes it possible to obtain an instance of either of two implementing classes, Folder and File, depending on the parameter value.

6 BETTER DECOUPLING WITH FACTORIES

The use of factories in interfaces can eliminate coupling between client code and library code. Consider, for example, the JAVA collection libraries. The standard library designers

Figure 5.5: An interface with a factory.

require, in very strong words, that interface types (like List and Set) will be used for method arguments:

". . . it is of paramount importance that you declare the relevant parameter type to be one of the collection interface types. Never use an implementation type."
– [3, p.526]; emphasis in the original.

Similar recommendations apply to return types, field types, etc., all in spirit of Canning et al.’s original suggestions for separating the type and class notions using interfaces [4]. The coupling of client code to concrete implementation is indeed reduced by following this recommendation. But, such a coupling still remains, particularly at the point where a client is required to create an object.

Interfaces with factories can eliminate this coupling. In the case of the List interface, clients can generate instances of some default implementation by writing (say) new List(). The factory can choose the proper concrete implementation, possibly based on hints provided by the client. Fig. 6.1 provides an example factory that can be used by the List class in Java’s collections framework. Should new and improved implementations appear in future versions of the Java class libraries, this factory can be upgraded, and all clients will immediately benefit from the change. This solves the change advertising dilemma for new implementations of interfaces.

Figure 6.1: One possible factory for the List interface.

We would like to draw attention to the fact that following the recommendation of using interfaces rather than classes as method parameters, may in some situations increase the burden on clients rather than reducing it. Consider the learning effort of a user in search of a specific service in a software library. Suppose that this service is provided by a method m in an interface I. Then, before m can be invoked, the user must search for all the different implementation of I, say classes C1,C2,C3, . . ., study them, and choose which of these to instantiate in order to generate an instance of I. Further, suppose that m takes a parameter of type interface I'. Then, the user must also search for all implementations of I0, say classes C'1,C'2,C'3, . . ., study them all and choose the one appropriate for instantiation prior to invoking method m. If the constructor of the chosen class expects a third interface parameter I'', then, the user must further search for implementations C''1,C''2,C''3, . . . of I'', etc.

A small example is method Security.getProviders in the Java standard library taking a Map as a parameter. In this parameter, the user can provide a set of selection criteria. Before the method may be used, even for testing or experimentation, the programmer must create an object representing such a test, and to do so, choose an implementation of the Map interface—but there are no less than seventeen such implementations in version 1.5 of the JDK.

Another example is method JPanel.setBorder() from the Swing GUI libraries, which expects a parameter of the Border interface. In order to use this method, the client must be spend time in studying the different implementations of this class, only to realize that yet a third class, BorderFactory, should be used to generate instances. With factories, the functionality of BorderFactory can be embedded in Border.

Searches for implementations of a given interface is usually not easy: implementations may be done by various different vendors, the list may change over time, and the selection between these may require a hefty learning effort. Interfaces (and abstract classes) with factories can therefore simplify the adoption of new, unfamiliar classes. Sometimes such a search is inevitable, but in many cases, it can be saved if the interface itself provides a reasonable implementation.

Writing a unit test code for a class whose methods take interface parameters is greatly simplified if these interfaces give ready-made instantiations. It is even conceivable that interfaces provide a stub implementation just for this purpose. For example, the standard Java interface Runnable can provide a stub implementation (perhaps defined as an inner class) in which the run() method does nothing.

7 CLIENT-SIDE FACTORIES

All examples so far defined factories in the same class on which the overload takes place. Factories of this sort are called supplier-side factories. It is also possible to define client-side factories, as demonstrated in Fig. 7.1.

Figure 7.1: A client-side factory for Accounts in class Bank.

Line 2 in the figure starts the definition of a factory. Unlike the previous examples, this definition specifies the returned type. The semantics is that the definition overloads new when used for creating Account objects from within class Bank. It is invoked in the evaluation of an expression of the form new Account(c) (where c is of type Customer or any of its subclasses) in this context. This factory chooses an appropriate kind of Account depending on the particular business rules used by the enclosing class.

Unlike supplier-side factories, client-side factories are inherited by subclasses. Therefore, the factory from Fig. 7.1 will also be used for evaluating expressions of the form new Account(c) in subclasses of Bank.

This client-side factory can be used by other classes as well, by writing Bank.new Account(· · · ), or, after making a static import of class Bank, by simply writing new Account(· · · ).

Fig. 7.2 shows an implementation of the ABSTRACT FACTORY pattern with static binding. Classes MotifWidgetFactory and PMWidgetFactory each overload the new

Figure 7.2: Widget-factory classes defined using client-side factories.

operator of all the GUI widgets. A client wishing to use Motif, may write import static MotifWidgetFactory.*. This may be changed later to import static PMWidgetFactory.*, should the GUI library need replacing.

The full semantics of a new call can now be explained as follows: Whenever a class is used in a new expression, its supplier-side factories enjoy an implicit import static. A client-side factory in scope can override this import.

The abstract widget factory example we have just described suffers from the problem that switching from Motif to PM requires a change to the client’s import static statements. But there may be many such statements, in many source files. The remedy is to simply define an empty class,

class WidgetFactory extends PMWidgetFactory {}

and statically import it in all clients. This will direct all widget factory calls to PMWidgetFactory. The GUI can now be globally replaced with a single change, specifically replacing WidgetFactory’s superclass.

Dynamically Bound Factories

The above WidgetFactory can be thought of as a statically-bound implementation of the Abstract Factory pattern, in that the decision on the concrete implementation is made at compile time. To make a dynamically-bound widget factory, we need dynamically-bound factories. These are defined, as the name suggests, without the static keyword. Fig. 7.3 shows how such factories can be used in the classical implementation of the Abstract Factory design pattern.

Figure 7.3: Using non-static factories to implement a dynamically bound abstract factory class.

Fig. 7.3(a) shows the abstract factory, while Fig. 7.3(b) shows two concrete implementations. The factories of the widgets are all non-static and obey a dynamic binding scheme. Also worthy of note is the factory of this abstract class itself, which (while realizing the Singleton design pattern) determines at runtime the correct GUI library.

Fig. 7.4 shows how dynamically-bound factories can be used to implement the Factory Method pattern (also known as Virtual Constructor). The code in this figure implements the classic example (from [10, p.107]) of an abstract Application class, bound to an abstract Document class. Each concrete subclass of Application can bind itself to a concrete subclass of Document, by overriding the dynamically-bound factories. The resulting code is very similar to the original GoF example, except that the

Figure 7.4: Implementing pattern Factory Method with dynamically bound factories.

newDocument method uses ordinary construction syntax (implemented using our notion of a factory) rather than the nonstandard "factory method" dictated by the pattern.

Syntactically, the invocation of a dynamically-bound factory defined in class C for objects of class S is written as c.new S(· · · ), where c is an instance of class C. The prefix "c." can be dropped for code inside class C (so it is replaced with this).

It is not a coincidence that this looks very much like the Java syntax for creating an instance of a dynamic inner class: c.new I(· · · ), where c is an instance of the containing class (possibly this) and I is the inner class’s name. The constructor of a (non-static) inner class in Java is a method of the containing class, and not of the class it constructs— just like a client-side factory is a member of the containing class, and not of its target class. In fact, Nystrom, Chong and Myers [16] have shown that if the concept of inner classes is extended (using nested inheritance), most of the need for the Factory Method design pattern disappears. But while nested inheritance has many distinct advantages with regard to code modularity and the creation of extensible software systems, it only solves the need for factory methods for classes defined inside the same module as their client. Also, it does not remove the need for instance-management patterns like Instance Pool or Flyweight.

8 DISCUSSION

Factories may be a minor perturbation to language syntax, but they are of benefit to langauge designers and programmers alike. We implemented factories as a Java extension using the Polyglot [17] extensible compiler framework (v. 2.0a4). This took approximately two workdays of a single programmer.

In our implementation, supplier-side factories (both explicit and AGFa) are realized as methods named $new$ in the container class. The return type of $new$ is the containing class itself.

Client-side factories are stored in the client, and are named $new$classname, where classname is the fully-qualified target class name, with every dot replaced by $dot$. For example, the factory for Accounts in class Bank (Fig. 7.1) is realized as a method called $new$com$dot$bank$dot$Account (assuming Account’s fully qualified name is com.bank.Account). The return type of client-side factories is the target type (e.g., Account).

Any use of new is replaced by the proper method invocation, wrapped in a test that ensures a non-null value is returned (and throws an exception otherwise).

The addition of factories to interfaces is less straightforward, since interfaces in Java cannot contain any concrete methods. Instead, our extension, synthesizes an static inner class (called $NewHolder$) for the interface, and places factories in this class.

The implementation generates bytecode that can be used on any Java virtual machine. As discussed in Sec. 5, the introduction of AGFas implies that Java-with-factories is fully compatible with existing JAVA source code. However, the code generated by our compiler assumes that all instantiated classes have been compiled using the same compiler, and thus have supplier-side factories (either explicit or AGFa). If factories are integrated into the Java language, full backwards compatibility with existing, pre-compiled classes can be achieved by having the class-loader (rather than the compiler itself) add any required AGFa to each class. This will work equally well for old and newly-compiled classes.

Clearly, the notion of factories is not limited to Java alone. It is not so difficult to approximate supplier-side—but not client-side—factories in Smalltalk, by overriding the new class method. Adding factories to C# seems rather straightforward, but it might take some cunning to add them to C++, since the language introduces two obstacles: First, C++ intrinsic overloading of the new operator, is focused on the memory allocation problem rather than on instance generation. One possible solution is to introduce a new keyword, such as factory, to the language. Declarations for factory new can then exist alongside those for operator new. Such definitions can include both supplier-side factories (no explicit return type) and client-side ones (with a specific return type). Client calls to new will then be redirected to the factory, and should the factory decide to create a new instance, the new operator will be used for memory allocation (as before).

The second obstacle is due to C++ value semantics. The compiler must know the space requirements of class instances allocated e.g., on the stack, but this is not possible with factories. A simple solution is that classes with factories are restricted to reference semantics representation only.

The Eiffel language presents a different challenge for introducing factories. Unlike constructors in C++ or Java, creation procedures in Eiffel are named. The advantage of this approach is that the distinction between the different kinds of objects that may be created is not by the kind of arguments, but rather through a meaningful name.

In terms of syntax design, the problem is that we must find a way, other than a special name, to distinguish between factory methods (which have no object to work on), and methods and creation procedures (which start their work with a system-supplied object.)

We propose to the integration of factories into Eiffel by introducing a new part to the Eiffel class declaration, alongside features, creates, etc. The part is called factory, and it may be included only in non-expanded types. Following Eiffel’s accessibility rules, a class may provide different factories to different client classes by qualifying the factory part with a type list. Supplier-side factories have the return type"like Current"; any other return type indicates a client-side factory.

A subclass may re-classify a creation procedure as a factory (or vice-versa) when overriding it, and in particular, the default creation procedure, default_create (defined in the root class ANY) may be changed to a factory by any class that so desires. Following the principle of uniform access, clients that include a creation instruction (or a creation expression) employ the exact same syntax regardless of whether a creation procedure or a factory is being used. The syntax !!x.make is used by clients to obtain an instance, regardless of whether make is a creation procedure or a factory. Interestingly, the distinct name for each factory and creation method implies that this extension maintains backwards compatibility with existing code, without resorting to automatically-generated factories (AGFas).

Fig. 8.1 shows an Eiffel version of the singleton class from Fig. 5.1. This class reclassifies default_create as a factory, so clients can use the creation instruction !!x (for a variable x of type S_TEMPLATE) to obtain the shared instance.

As we can see from the figure (line 7), no special syntax is needed to create an instance from inside the factory (the equivalent of the special this() call in the Java version): Since a class may include both creation procedures and factories, each with distinct names, there is no risk of undesired recursion. Whenever a new instance is required, the factory simply calls a (possibly private) creation procedure.

Figure 8.1: A singleton defined in EIFFEL using a factory.

Footnotes

1 http://www.springframework.org

2 but also a "template for several objects . . . [a description of] how these objects are structured internally"

3 Even for virtual methods.

4 Such methods are sometimes called factory methods. While serving a similar purpose, they are different


REFERENCES

[1] G. Booch. Object Oriented Design with Applications. The Benjamin/Cummings Publishing Company, Inc., 1991.

[2] T. A. Budd. An Introduction to Object-Oriented Programming. Addison-Wesley Publishing Company, first ed., 1991.

[3] M. Campione, K. Walrath, and A. Huml. The Java Tutorial: A Short Course on the Basics. Addison-Wesley Publishing Company, 2000.

[4] P. S. Canning, W. R. Cook, W. L. Hill, and W. G. Olthoff. Interfaces for stronglytyped object-oriented programming. In OOPSLA’89.

[5] C. Chambers. The Cecil language, specification and rationale. Technical Report TR-93-03-05, University of Washington, Seattle, 1993.

[6] L. G. DeMichiel, L. U. Yalc¸inalp, and S. Krishnan. Enterprise JavaBeans specification, version 2.0. http://java.sun.com/j2ee/, 2001.

[7] ECMA International. Standard ECMA-367: Eiffel Analysis, Design, and Programming Language. ECMA International, 2005.

[8] M. A. Ellis and B. Stroustrup. The Annotated C++ Reference Manual. Addison-Wesley Publishing Company, 1994.

[9] M. F¨ahndrich and K. R. M. Leino. Declaring and checking non-null types in an object-oriented language. In OOPSLA’03.

[10] E. Gamma, R. Helm, R. E. Johnson, and J. M. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Publishing Company, 1995.

[11] J. Gosling, B. Joy, G. L. J. Steele, and G. Bracha. The Java Language Specification. Addison-Wesley Publishing Company, third ed., 2005.

[12] M. Grand. Patterns in Java, Volume 1. John Wiley & Sons, 1998.

[13] I. Jacobson. Object-Oriented Software Engineering - A Use Case Driven Approach. Addison-Wesley Publishing Company, first ed., 1992.

[14] K. Marx. Das Kapital: Kritik der politischen Oekonomie. Otto Meissner, 1867.

[15] B. Meyer. Object-Oriented Software Construction. Prentice-Hall, Englewood Cliffs, New Jersy 07632, second ed., 1997.

[16] N. Nystrom, S. Chong, and A. C. Myers. Scalable extensibility via nested inheritance. In OOPSLA’04.

[17] N. Nystrom, M. R. Clarkson, and A. C. Myers. Polyglot: An extensible compiler framework for Java. In CC’03.

[18] M. Odersky, P. Altherr, V. Cremet, B. Emir, S. Maneth, S. Micheloud, N. Mihaylov, M. Schinz, E. Stenman, and M. Zenger. An overview of the Scala programming language. Technical Report IC/2004/64, EPFL Lausanne, Switzerland, 2004.

[19] D. L. Parnas. Information distribution aspects of design methodology. In IFIP’71.

[20] B. Shannon. Java 2 platform enterprise edition specification, v1.4. http://java.sun.com/j2ee/j2ee-1 4-fr-spec.pdf, 2003.

[21] B. Stroustrup. The C++ Programming Language. Addison-Wesley Publishing Company, third ed., 1997.

[22] N. Wirth and M. Reiser. Programming in Oberon—Steps Beyond Pascal and Modula. Addison-Wesley Publishing Company, 1992.

About the authors

Tal Cohen is a computer science Ph.D., currently employed in Google’s engineering center in Haifa. This research was done during his Ph.D. studies in the Technion in Haifa, Israel. He can be reached at ctal@cs.technion.ac.il. See also http://tal.forum2.org/cv.

Yossi Gil is on the faculty of the department of computer science at the Technion and head of the software and systems development laboratory there. His research interests include software engineering, programming languages and database systems. All of Gil’s academic titles were conferred by the Hebrew University of Jerusalem: B.Sc. (summa cum laude) in mathematics and physics, M.Sc. (summa cum laude) in computer science, and Ph.D. in computer science. He can be reached at yogi@cs.technion.ac.il.


Cite this article as follows: Tal Cohen and Joseph (Yossi) Gil: "Bettter Construction with Faxtories", in Journal of Object Technology, vol. 6, no. 6, July-August 2007, pp. 103-123 http://www.jot.fm/issues/issue_2007_07/article3

 


Previous article

Next article