.

My design principles

This old text from around 2007 documents my journey back then. While I don't do things in exactly in that way anymore, it might be of interest to somebody out there.

This document does not introduce any generally new ideas about software design. It shows my personal focal points: Where I differ from most people and what I think should be done differently in most software projects.

The most fundamental object oriented design principles are to program to an interface, not an implementation, and to encapsulate object creation from clients. These are two of the most important messages of "Design Patterns" [Gamma95], stated in chapter 1 and applied in chapter 2 and 3 (although not consequently). The primary reason for bad design in production software still lies in violation of these principles, rather than in any more advanced, fancy design issues.

Program to an interface

One of the principles that is quoted often but rarely followed is "program to an interface, not an implementation".

Consequences

A consequence of this principle is that all types should be interface types rather than concrete or abstract class types. (This applies to languages with an explicit interface type like Java or C#, but it can easily be derived to other programming languages.) This includes the type of a reference during declaration, a return type or a method parameter type. The only exception to this rule is when you have to adjust to an API that violates the rule already. This exception applies to almost any rule in this text.

Common justifications for not using an interface type

"There will always be only one implementing class. We can as well use it as the type."

General problems with this way of thinking are:

"The API is exposed only to myself / our team."

If someone (a person, a development team, ...) is his own client, then this changes nothing about the rightness of following the proven principles of good object oriented software design. The quantity of the damage done by violating them is lower in that case, but the quality of damage is still the same: Wanting to violate the principles might be an indication that the design is not a good, object oriented design in that it uses inheritance, encapsulation and polymorphism as a primary basis to solve problems, and the need for a complete redesign might be the consequence, rather than a smooth evolution of the software.

Only classes can truly be immutable

In many situations it is appropriate to use immutable objects (see Bloch01, Item 13: "Favor Immutability" - highly recommended!). Immutable objects are simple, thread-safe, can be shared freely. Thus they can well be used as building blocks for other objects without much things to be considered.

If such an immutable object is based on a final class like java.lang.String, then there is nothing to fear. If it is based on an interface then there can always be clients that make a mutable extension of it! Other implementations that rely on the immutability of the passed object might start to behave in an unexpected way.

In the following example the DogUser implementation depends on the immutability of Dog:

immutable interface?

Dog is an immutable type - at a first glance

DogUser and Dog are both part of the same library / framework. Now consider a client of this API who extends Dog:

broken immutability by subclassing

the client made his own mutable subinterface

The client can pass instances of BadMutableDog implementations to DogUser#addDog and later change the state of the passed argument. Here is an example of a client implementation that would most likely not be expected by DogUser:

        DogUser dogUser = ...;
        BadMutableDog d = ...;
        d.setName("Dog 1");
        dogUser.addDog(d);
        d.setName("Dog 2");
        dogUser.addDog(d);
    
Now DogUser has only one reference to a Dog named "Dog 2" if he uses some kind of set (no duplicate elements), or two, both named "Dog 2", if its container allows duplicate elements. In many cases this may not be what the API or the client of the API had in mind (in many cases it will be exactly what they had in mind -> no problem there, no discussion here).
Solutions to the problem of broken immutability
Use an immutable final class type
This solves the problem at hand. It is the safest, compiler-enforced guarantee that clients will use the type as intended. Classes that take a final class Dog can rely on its immutability. The disadvantages are ... well, this is what the entire article is about.
Make a defensive copy of the passed parameter
This means not to rely on the immutability of Dog, and not reusing the instance. Safe, but all the advantages of using immutable objects are gone. In most cases, this defensive copy will be redundant.
      public void addDog(Dog d) {
          d = make copy of d;
          add d to dogs;
      }
    
Make a contract for clients to follow
The provider of the Dog interface could specify in the documentation that no mutable subinterface is to be used for certain other parts of the API. Then clients can use an other way of making a mutable version of Dog, or it can be provided along with the API. Generally this common problem indicates that it is often best to make the mutable companion type of the immutable type not as a subinterface of the immutable version (as the BadMutableDog above) but rather as something that implements all its interface, plus some interfaces for the mutability:

preserved immutability

the mutable companion class of Dog preserves immutability

This solution combines all advantages of immutable objects with the advantages of using only interface types. Its major drawback is that in most common object oriented programming languages such a contract cannot be checked by the compiler. It can only be documented. If clients violate the contract anyway then the results can be strange, problems might occur sporadically and might be hard to debug.

Only abstract classes allow the addition of methods later (Java / C#)

This argument is correct, but overestimated. (You should be very well familiar with it, because there is a lasting discussion and plenty of dispute about it. See for example [Bloch01] or the interview of artima with Erich Gamma.)

In most cases in which the desire for adding a method to a type arises, it is one of the following situations:

Less fundamental implications

Generally it makes sense that an interface either declares one (at least one) method or that it inherits methods from two (at least two) other interfaces. I follow the general reasons against "marker interfaces": In most cases it is metadata. A marker interface is not an appropriate way to define such metadata. In Java 5.0 for example one would better use an annotation. If the language provides no such way to specify metadata then it is still better to do so in the code documentation instead of using a marker interface.

simple interface inheritance
good: inherits operation(s) and declares operation(s)
marker interface (does not add any operations)
bad: does not add any operations
double interface inheritance
good: does not add any operations, but joins operations of other interfaces

A class should generally implement exactly one interface if it can be instantiated, zero otherwise. I don't see a fundamental violation of a principle by implementing more than one interface, but usually there is no justification, no need to do so.

Concrete inheritance (inheriting from a class) should be used far less than it is today. An interface inheriting from one or more interfaces is a fundamental construct in an object oriented design, but a class extending an other class is not. It is just a convenience construct most of the time. Strictly spoken, it violates encapsulation: The class is bound to be implemented by inheriting from an other class for all times. There are only very few - if any - good uses of subclassing. Using abstract classes indicates a design flaw in most cases. Hardliners don't use abstract classes at all, and they make all their classes final.

concrete inheritance
bad: uses concrete inheritance
delegation
good: uses a helper class using delegation

Encapsulate instance control

Basic instance control

An API should expose no instantiable classes. Static factory methods should be exposed instead of public implementations and public constructors. A typical factory class in Java would be public or package level, final, and have a private constructor that throws UnsupportedOperationException. It would not implement any interfaces, and all its methods would be static.

simple factory

a simple factory as an alternative to a public class and constructor
public final class PersonFactory {
    private static final class PersonImpl implements Person {
        //...
    }

    private PersonFactory() {
        throw 
            new UnsupportedOperationException("no instances should be created");
    }
    
    public static Person createPerson(String name, int age) {
        return new PersonImpl(name, age);
    }
}
    

In doing so an API does not expose to its clients when, how and at what quantity objects are created.

All reasons for choosing this design as the general approach rather than only where it appears to solve a concrete problem at hand had been provided in "Design Patterns" [Gamma95] 1995 already. (They did not consequently apply this principle in their examples and patterns, though.)

Further references

See "Effective Java" [Bloch01], Item 1 for a discussion of this topic where the conclusion differs from mine.

Abstract Factories

In many situations it pays off to further abstract the creation process. The static factory method would then not directly return the product. Instead it would return the actual factory to return the product (see also Gamma95, "Abstract Factory" and "Factory Method"). The static factory method would not take the parameters for the object to be created. Those parameters are instead being passed to the create method of the factory instance it returns. The benefits provided by this way support the thesis that extensive use of static methods can be a symptom for not really using an object oriented design. For object creation it is still better to expose a static factory method than to expose a constructor, but at least this method takes no parameters, as it does not create the product itself.

factory creator

an additional abstraction provides more flexibility

Example

For a GUI framework, let there be buttons, windows and things like that. It is not at all unlikely that alternative look and feels are being introduced later. Depending on the look and feel that is selected, it would be necessary to use a different method to create the button, window and so on. This check for the current look and feel would be distributed all over the client code that uses this API. With the additional abstraction described above, clients can create the appropriate factory in one place, and then use this factory to create the buttons, windows and so on.
public final class ButtonFactoryCreator {
    private ButtonFactoryCreator() {
        throw 
            new UnsupportedOperationException("no instances should be created");
    }
    
    private static final class ButtonFactoryImpl implements ButtonFactory {
        private static final class ButtonImpl implements Button {
            private final String text;
    
            public ButtonImpl(String text) {
                this.text = text;
            }
    
            public String getText() {
               return text;
            }
            
            //...
        }
            
        public Button createButton(String text) {
            return new ButtonImpl(text);
        }
    }
    
    
    public static ButtonFactory createButtonFactory() {
        return new ButtonFactoryImpl();
    }
} 
        
public interface ButtonFactory {
    public Button createButton(String text);
}

public interface Button {
    public String getText();
    // ...
}
    
The same structure would apply for other GUI elements like windows, menu bars and so on.

If we now want to add an additional look and feel, we would expose an additional version of ButtonFactoryCreator, e. g. BrightButtonFactoryCreator. The two exposed interfaces, ButtonFactory and Button, would not change.

In this particular example scenario, it might make sense to enforce that the different widget types cannot be mixed. This is not a difficult task, but it is beyond the scope of the thesis.

General Applicabiliy

There is a lot of dispute about when this design should be applied: Only when the concrete need for different product factories is there, or also when this need might arise in the future? And is the latter always the case; is it always possible that this additional abstraction might become necessary later? This discussion is similar to the one about when to use an interface as the type (see above).

Patterns

This variant of an Abstract Factory is the best basis for many creational patterns.
Abstract factories as method arguments
Passing an abstract factory to a method is an alternative to the Factory Method described in "Design Patterns" (Gamma95). The Factory Method pattern utilizes a callback method that relies on an abstract, not purely virtual class.

classic factory method

the Factory Method pattern of the GoF

That requires to expose an abstract, not purely virtual class to clients. This has many apparent drawbacks, which are accepted for the benefits this pattern provides:

By exposing a not purely virtual class instance control is given to the client. The client decides when instances are created, and he does not have much of a choice either. For each distinct behaviour of the Factory Method an appropriate class would be created and instantiated. (The client can work around that by implementing the abstract method in such a way that it calls a method on a type which can be changed using an other method the client adds to the abstract class. That is similar to the alternative I suggest, only that it is unnecessarily complicated, because this approach would solve the initial problem, which was supposed to be solved by the pattern itself.) This is only one of the many drawbacks which are a consequence of the violation of the principles described above.

In most situations where this pattern would be used there is a better alternative: The operation that relies on a product to be created by a client (or its object, e. g. by constructor parameter) can be parmaterized by an Abstract Factory type which has the FactoryMethod() to return the product. This way provides more flexibility by exposing less.

To stick with our abstract button example, consider a class that needs a way to create a button for its own internal use, but wants clients to specify the class of the button. In the classic Abstract Factory method this class would have an abstract method, createButton, which clients would implement. The far better alternative is to provide a way for clients to give it a reference to the ButtonFactory:

UML: passing an abstract ButtonFactory
passing a ButtonFactory

UML: abstract factory as method parameter

abstract factory as method parameter

The "Applicability" section in "Design Patterns" goes too far: For the situation that "a class can't anticipate the class of objects it must create" the Factory Method is not the right pattern to be used. As for the other applicability statements, it failed to describe the actual (higher level) requirements, which have been misinterpreted to "a class wants its subclasses to specify the objects it creates", which is a requirement defect (see the principles this article - and also the GoF book itself - is based on).

Singleton
The problem with the Singleton pattern is that while it does at least not expose a constructor, it exposes an instantiable class. Replace it with a static factory method or with an abstract factory. (The method that returns the actual product would then only create it once and always return the same. Wether this is part of the documented contract of the API or just the current implementation depends on the requirements.)

Further References

In "Design Patterns" (Gamma95) there is a similar discussion in the chapters about the Abstract Factory pattern and the Factory Method pattern. I just disagree with a lot of what they write, which is why I decided for a fresh start in my arguments instead of building upon that. Those chapters are more complete, though.

I have seen this design for the first time in the ContractualJ API.

References

Gamma95: "Design Patterns - Elements of Reusable Object-Oriented Software"; Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides; 1995 Eddison-Wesley

Bloch01: "Effective Java - Programming Language Guide"; Joshua Bloch; 2001 Sun Microsystems