/ LANGUAGES, DESIGN, OBJECT ORIENTATION, FUNCTIONAL PROGRAMMING

Dissolving Design Patterns In Design Elements

The book Design Patterns: Elements of Reusable Object-Oriented Software was one of the texts that changed how we think about software design. This book came out in 1994 through the efforts of Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, later remembered as Gang Of Four (GoF)

GoF Design Patterns: The Origin, the Impact

The book’s importance lies in its effort to review the software production done up at that moment, giving more structure to the concept of Software Design. At the same time, it reports the schematization of recurring problems and their possible solution families in the form of design patterns. The text has become a controversial element today, sometimes demonized. It’s an important work, no longer modern, which is misunderstood today due to the need for pragmatism compared to the book’s analytical content.

The less known Vision: the Meta-Patterns

A contribution was also produced in 1994 that did not become as famous as the GoF book but gave rise to different areas of research until 2015; let’s talk about a paper by Wolfgang Pree that appeared at the European Conference on Object-Oriented Programming (ECOOP): Meta patterns—A means for capturing the essentials of reusable object-oriented design.

This work’s peculiarity is reviewing Design Patterns and considering their usage in software frameworks. This perspective highlights that Design Patterns are micro-architectures or architectural components of a framework.

More research works have used this paper as a starting point to discover design patterns expressed in production frameworks and make possible other kinds of manipulations or refactoring.

In his paper, W. Pree looks at Design Patterns at a higher level of abstraction, where they are introduced:

  • Frozen Spot, consolidated points, parts of infrastructure
  • Hot Spots, points of variation, and extension of a given infrastructure

Frozen Spot refers to an infrastructure class, the so-called template class: one template class generally hosts a template method, which is the entry point for the extension classes. The hook class is a class that hosts a hook method that represents a point of extension and, thus, a Hot Spot. Combining the two types of classes and the related template/hook methods makes it possible to define meta-patterns. These allow us to understand the intimate nature of a framework.

Wolfgang Pree identifies specific families of meta-patterns:

  • Unification
  • Recursive unification
  • Connection
  • Recursive connection

Meta Patterns Classification

Using this classification, it is possible to see the GoF Design Patterns family from the meta-patterns perspective, determining the following mapping table.

Meta Pattern GoF Design Pattern

1:1 Connection

Bridge, Builder, mediator State, Strategy, Visitor*

1:N Connection

Abstract Factory, Adapter, Command, Flyweight, Iterator*, Observer, Prototype, Proxy, Visitor

1:1 Recursive Connection

Decorator

1:N Recursive Connection

Composite, Interpreter

Unification

Factory Method, Template Method

1:1 Recursive Unification

Chain of Responsibility

1:N Recursive Unification

None

No Meta Pattern included

Facade, Memento, Singleton

* : patterns including more than one meta pattern

The historical moment in which the paper was born shows the limits due to the use of inheritance in the OOP languages: the need for "implementation reuse" was a goal of those times. There’s also the lack of reference to Generics or strong-typing concepts. Despite these observations, W. Pree’s paper has evolved, and it’s a work that allows the Design Patterns to be integrated into more granular design components.

New points of contact and other designs can be found on the border with Functional Programming: let’s try to see how this idea develops.

Hot Spot, Frozen Spot, and High Order Function (HOF)

A higher-order function (HOF) is a function that does at least one of the following:

  • accepts one or more functions as arguments
  • returns a function as a result

This definition immediately takes us back to the classic concept of callback in the context of object-oriented development. Other forms of HOF are function composition and other constructs such as partial application and currying. These constructs allow us to use Meta Patterns as a guide to review Gof Patterns functionally.

An Example: The Template-Method

The most immediate example of a GoF Pattern that corresponds to Meta Patterns is the Template Method Pattern. The Template-Method pattern outlines an algorithm in a method, using specific steps that its subclasses can redefine. In a Template Method Pattern, the template method is final, and the steps are rendered protected. This is to show a single entry point to the algorithm, having extension points visible only to extension classes.

This pattern is classified in the family of Unification meta patterns since the template method is in the Template Class, together with the hook methods.

The Template Pattern

It is simple to sense that there is an immediate mapping to HOF: a template function takes, as parameters, other functions that represent the steps of the logic the template function hides. This reasoning leads us to the transition from inheritance to composition. FP has different assumptions, and we can use various forms of HOF, such as closure, function composition, or currying: we lose something in terms of encapsulation, but we enhance composition and laziness.

public abstract class SocialNetworkSession {
	public final boolean post(String message) {
		if (logIn(user)) {
			boolean result = sendData(user, message.getBytes());
			logOut(user);
			return result;
		}
		return false;
	}
	abstract protected boolean logIn(SocialNetworkUser user);
	abstract protected boolean sendData(SocialNetworkUser user, byte[] data);
	abstract protected void logOut(SocialNetworkUser user);
}

In a simple mapping to HOF, it’s possible to change the code as follows:

public interface SocialNetworkSession {
	default boolean post(
               SocialNetworkUser user,
		   String message,
	         Function<SocialNetworkUser, Boolean> logIn,
		   BiFunction<SocialNetworkUser, byte [], Boolean> sendData,
	         Function<SocialNetworkUser, Boolean> logOut) {
		if (logIn.apply(user)) {
			boolean result = sendData.apply(user,message.getBytes());
			logOut.apply(user);
			return result;
		}
		return false;
	}
}

It’s possible to use function composition to have a less imperative result as follows:

public interface SocialNetworkSession {
	static BiFunction<SocialNetworkUser, String, Boolean> post(
			 Function<SocialNetworkUser, Boolean> logIn,
			 BiFunction<SocialNetworkUser, byte [], Boolean> sendData,
			 Function<SocialNetworkUser, Boolean> logOut
			) {

		return (user, postBody)->
		           logIn
		             .andThen(isLogged ->  isLogged?sendData.apply(user,postBody.getBytes()):false)
		             .andThen(isSent-> logOut.apply(user))
		             .apply(user);
		}
	}

It’s possible to use currying to have a more fluent code, but in Java, as a strongly typed language, it’s obtained a more verbose type declaration as follows:

        Function<SocialNetworkUser,
		   Function<Function<SocialNetworkUser, Boolean>,
		    Function<BiFunction<SocialNetworkUser, byte [], Boolean>,
		     Function<Function<SocialNetworkUser, Boolean>,
		      Function<String, Boolean>>>>> post =
		         usr->lgi->snd->lgo->body->
		         {
		        	 boolean result = false;
		        	 if(lgi.apply(usr))
		        		result = snd.apply(usr, body.getBytes());
		        	 lgo.apply(usr);
		        	 return result;
		         };

		  post.apply(user)
		     .apply(logInFn)
		     .apply(sendDataFn)
		     .apply(logOutFn)
		     .apply(postBody);

But let’s pause for a moment! We can turn back to OOP and use a Template Class with an effective constructor, preferring composition over inheritance, and considering the tell don’t ask, making possible the following code:

var user = SocialNetworkUser.of("Stef", "pwd1");
var post = Post.by(user, "Test Message!");
var session = new SocialNetworkSession(new FacebookLogInOut(), new FacebookSendPolicy());
session.post(post);

Another Example: The Command Patterns

The central aspect of this pattern is an abstract Command class that includes an abstract execute operation. Concrete Command subclasses specify the receiver storing it in an instance variable and by implementing execute to invoke the request. The receiver has the knowledge required to carry out the request.

The Command Pattern

The Command Pattern is not only the command hierarchy but also the collaboration with Receivers and the disjointed points defined by the Invoker.

Different needs can arise, and the Command Pattern can be the answer to different situations:

  • sometimes, it’s needed to execute a sequence of commands or a transaction can be simulated
  • sometimes, it’s needed to replace commands dynamically
  • sometimes, it’s needed to support command scripting

In the Meta-Patterns classification, the Command is a 1:N Connection since the Invoker can be related to multiple command objects whose execution it determines.

This pattern can become an example of HOF, where the Invoker element is a function that takes another function, the Command, as input. Wikipedia reports the same idea:

   ...The central ideas of this design pattern closely mirror the semantics of first-class functions and higher-order functions
   in functional programming languages. Specifically, the invoker object is a higher-order function of which the command object
is a first-class argument...
public interface Invoker extends Consumer<Command>{
	static Invoker create()	{
		return command->command.run();
	}
}

public interface Command extends Runnable{
	static Command defineFor(Receiver receiver){
		return ()-> receiver.activity();
	}
}

Receiver receiver = ()->System.out.println("Some Activity done!");
var invoker = Invoker.create();
invoker.accept(Command.defineFor(receiver));

It’s also possible to rewrite the code in another form: this shows that closures are a possibility to relate different elements:

Receiver receiver = ()->System.out.println("Some Activity done!");
Command cmd = ()->receiver.activity();
Invoker.create().accept(cmd);

This picture is still incomplete: we need to consider that a command could have a return value to inform the success or failure of the operation; it can also have other information for possible compensatory actions. These additional aspects can alter the implementation of both OOP and FP realization.

Conclusion

The post shows that Design Patterns are more than just pre-packaged solutions. They can be a moment of analysis to highlight design elements in a given context. The 1994 book of GoF is only a starting point because it expresses both families of solutions and a methodology for highlighting and describing micro architectures linked to a problem. Meta patterns help us understand this reality and give us an additional tool: a design becomes an enabler for a family of solutions by identifying the expandability points and composing elements based on a context. Different realizations can exist using different paradigms that, in turn, provide feedback on which elements to consider as enabling the expandability or modularity of our designs.

Reasoning arises about AI: our design decisions, initial classifications, and different realizations can become the training for specialized AI that can help us review, transform, and verify the frameworks and products that will be realized. We may arrive at an Engineering expression with fewer heuristics and renewed principles.

Stefano Fago

Stefano Fago

Informatic passionate about distributed systems, software design, and software architectures experiences as a consultant (in different contexts) and trainer (Java/J2EE), software designer, team leader, and speaker in some Italian meetups; interested in too many topics! Aficionado of the Commodore64 and I'm a bassist/singer as soon as I've free time

Read More
Dissolving Design Patterns In Design Elements
Share this