Object-oriented Programming (OOP)

Frameworks

Principles

Class Libraries

A class library is an assembly / collection of mostly individually usable classes. They are used to define types in the class library instead of in the programming language. They follow the Open-Closed principle:

  • Closed: Class is a stable component -> creation and usage of instances
  • Open: Class is an adaptable component -> deriving subclasses from it without changing the base class

Classes from a class library are mostly used for instance creation. Class libraries do not provide an architecture or enforce control flow.

Framework

A framework defines architecture and control flow of a software system. It consists of a set of abstract and concrete classes (similar to class library). It provides a generic solution for similar problems in a specific context. They are thus context dependent (e.g. server applications, desktop applications). The whole architecture is reused (rather than individual classes).

The framework provides predefined extension points to adapt it to the context it is applied to. This is accomplished by subclassing / parametrization / composition. They offer the highest degree of reuse: Analysis (of problem domain), Design, Implementation.

In regular application, the application has the control flow. However, when a framework is used this is inversed. The framework controls the flow. Also referred to as "Hollywood Principle" (Don't call us - we will call you.).

Design Patterns vs. Frameworks

Design patterns are much more abstract than frameworks. They can only be exemplified as code (provide examples of a pattern) and always have to be implemented from scratch to fit the context. A framework in contrast is available as an implementation.

Design patterns are substantially smaller than frameworks. But a good framework usually employs design patterns.

Furthermore, design patterns are less specialized. They are usually independent from the problem domain whereas frameworks are targeted at a specific application domain (e.g. framework for mobile applications).

White Box vs. Black Box Frameworks

There are two types of frameworks: White box and black-box frameworks. They distinguish themselves in the way a framework is used while developing an application. A white-box framework is used by subclassing (i.e. I need to write code to employ it).

White-Box Frameworks

They are reused by inheritance. Program code is used rather than configuration. The template pattern is frequently used. They are static (since they make use of inheritance), the configuration of the system is defined at compile time.

They usually have a simple design. This is due to the flexibility of inheritance (i.e. it can be used to override many things). Consequently, they can cope with unforeseen requirements and are thus suited for immature domains.

They define "Hot Spots": Predefined intervention points. The elements where we can make changes. (In contrast to this: Frozen Spots). A disadvantage of white-box frameworks is their static nature. Additionally, inheritance has to be used (subclasses need to be built). This subclass releationship also means a tight-coupling between the application and the framework. As a consequence, the development of the framework itself becomes cumbersome. Whenever the base classes in the framework are changed, the clients also must be adapted.

Black-Box Frameworks

They are reused by composition and delegation. The inner workings of the framework are not of interest. The framework is configured instead of programmed. The configuration can happen in XML / JSON files or in more complex cases interfaces need to be implemented that provide the class/object as a configuration parameter. These configuration classes can be context dependent (e.g. differ in testing / production environments). One advantage of the black-box framework is that configuration happens at run-time (i.e. they are dynamic). Also the maintenance is more simple because there are less dependencies. However, the design is much more complex. The configuration is less flexible and thus prediction of changeabilities required (How will me application need to evolve in the future?). Black-box frameworks are thus better suited for mature domains.

Usually frameworks start as white-box frameworks and gradually turn into black-box frameworks. Grey-box frameworks are common.

Configuration

We basically need to configure when and how the required objects (=dependencies) are created. There are a number of different ways to achieve this:

New Class()

  • Simple: default way of creating objects in Java
  • Type of instance is defined at compile time (constructor can only return one type)
  • Strong coupling between creator (=client) and implementation
  • No factorization

Factory

  • Encapsulation and factorizing of instance creation
  • Client only knows the factory and gets its instances from there
  • Instances are controlled and may be subtypes
  • Object creation is still defined at compile time
  • Providing the configuration in a file is still not sufficient

Factory Method

  • Framework is responsible for triggering the object creation
  • Subclassing increases coupling between framework and application
  • Typical for white-box frameworks

Dependency Injection

Dependency injection solves the issue of object creation. The creation of objects is completely delegated to the framework. The code just defines which objects need to be created and the rest is handled by the framework.

// there might be different implementations for this interface
// e.g. for production / testing 
public interface ICustomerService {
  public void addCustomer(Customer customer);
  public Customer getCustomer(int id);
  public void deleteCustomer(int id);
  public void updateCustomer(Customer customer);
  void reset();
}

Without dependency injection we usually create a factory:

public class ServiceFactory {
  // service is created as singleton
  private static ICustomerService customerService;
  public static ICustomerService getCustomerService() {
    try {
      if (customerService == null) {
        customerService = (ICustomerService) Class.forName(getProperties()
           .getProperty("ServiceFactory.CustomerService")).newInstance();
          // class is created by a reflection, is defined in property file
      }
    } catch (Exception e) {
      System.out.println("Class not found");
    }
    return customerService;
  }
}

This approach is sufficient for simple dependencies. As soon as it gets more complex, it gets very cumbersome. The code below exemplifies an use case where the class CustomerService requires an instance of ILogger:

public class CustomerServiceProd implements ICustomerService {
  public CustomerServiceProd(ILogger logger) {
    // ...
  }
}

If we imagine this scenario for more dependencies, then this can lead to a recursive tree of dependencies that need to be created. This becomes cumbersome when factories are used.

Dependency injection allows that a client only needs to specify the implementations and their dependencies and the framework handles the creation of them. This injection can be accomplished via: Constructor, setter or interface. Another advantage is that the number of instances can also be configured (e.g. singleton per VM / per session / per request).

Advantages of Frameworks

The biggest advantage is that they provide a codified guideline for the application and thus provide guidelines for the development of the application. The can be look at as "guard rails" for developers that enforce proper standards. This simplifies the development since there are less decision to be made.

Furthermore, they provide a massive development head start. They solve many problems in advance and contain the codified know-how of experienced designers / developers. They use a proven design that has been tested in numerous applications. They are reuse at its best (e.g. Spring Boot application can be created in 5min - has everything, can do nothing).

The also simplify the maintenance. The applications (own code) are considerably smaller. Several applications may share the same framework. Also the code of the framework itself is usually maintained by other developers or a development community.

Framework Development

Now the question arises: How do I develop a framework? The starting point is usually repeated implementation in a domain (e.g. multiple similar applications have been developed that solve similar problems). In a next step the reusable items are identified and common code is refactored. This usually requires introducing abstractions and generalizations. After this step the framework needs to be verified by adapting existing applications to ensure that the framework is really reusable and applicable.

Development of (large) frameworks in advance is tough and risky and some efforts fail due to the immense complexity (e.g. Taligent or San Francisco).

The challenge is that applications and frameworks are interdependent and this greatly affects (i.e. hinders) the evolution of a framework.

Challenges

Usually frameworks are language dependent since they are implemented in a single programming language. Exceptions are only feasible with similar binary / object models (e.g. .net languages supported on Java VM).

They are also domain specific since they are only applicable in the domain it was designed for. It is not recommended to develop "against" a framework (i.e. make it work in ways it was not designed to work).

Documenting frameworks can be a challenge. Design patterns are helpful to convey ideas. To explain a framework statically is quite difficult since the interaction of objects is key. This can be more difficult to explain. Examples and Cookbooks (e.g. How to solve XY) can help. The documentation should be written from the problem domain: I have problem X. How can the framework solve it?

Debugging of applications can be cumbersome (e.g. the stack might be "polluted" with framework classes). The framework must be available as source code and sometimes it is necessary to comprehend the internals of the framework.

The development effort of the framework itself is huge. It is usually better to develop an application with a framework than to develop a framework. Maturing a framework takes time and thus large and successful frameworks are rare (Spring is a notable exception). The framework investment may only pay off with extensive reuse.

Sometimes the learning curve for a new framework can be steep. Also reusing a framework is way harder than reusing a single class. Cookbooks and examples are essential.

Summary

There are different types of reuse:

  • Components: concrete, less flexible, usually little adaption needed / possible.
  • Framework: abstract and concrete, flexible and adaptable
  • Design: abstract, implementation missing

There are white-box vs. black-box frameworks but in most cases the frameworks are differently weighted hybrids (grey-box).

Frameworks (and class libraries) are essential for leveraging development but they are expensive to develop and maintain.