Decorator and Strategy Patterns
Motivation
Release 5.0 of uPortal brought major changes to tooling, deployment, and split the uPortal repo in two. While there were several other changes, like renaming all the packages from org.jasig to org.apereo, the tooling, deployment and repo changes have a significant impact on customizing uPortal.
uPortal 5 certainly supports modifying the skin to match your desired branding. Deployers need to modify their skins to match local branding, so uPortal defines a Gradle task that creates a base skin to modify in uPortal-start, the new deployment repository. There is also direct support for configuration. With a combination of per-server properties files and overriding Spring Framework contexts, most config combinations are covered in uPortal-start. In fact, the focus of the uPortal-start repository is to capture these changes that really cannot be shared.
However, modifying Java code is now discouraged by uPortal 5’s design and repo changes. Why do we want to avoid Java changes? We have found that, historically, many Java changes were 1) useful to several deployers, but 2) were kept locally and not shared back with the uPortal Community. In previous versions of uPortal, it was too easy to modify the Java code since it was compiled every deployment. There was effectively no cost. Also, local changes would become lost because the source was in a single, huge repo. The effort to isolate these changes is often challenging due to upstream changes from the Community being merged into the local deployer’s repositories. This is all discernible, but it takes time, effort, and a bit of expertise. The impact of the uPortal 5 change to separate the Java code from the theme and configuration was two-fold. First, deployments will be faster and more consistent when the pre-built “code” binaries are pulled from a consistent source, namely Maven Central. Second, customizations need to be handled as separate modules or added to the official uPortal repository.
The focus of this series is on customizing the Java code and adding / modifying functionality as a separate module or enhancement to uPortal. In reality, there will be significant overlap. If a custom enhancement does not completely replace an existing uPortal class, a change to uPortal will be needed. Both can benefit from similar design considerations.
Design Patterns
A great starting point is Design Patterns by Gamma, Helm, Johnson, and Vlissides. It is the seminal book in this area of software development. There have been debates on whether there are really just a few true design patterns, or if there are a significantly more compared to the 23 detailed in the text. We will focus on two that are aptly useful with uPortal 5: Decorator, and Strategy.
Decorator Pattern
Decorator, also known as Wrapper, is the pattern that allows for attaching additional functionality to an object. It provides an alternative to subclassing, which is especially useful when the class in question is final and cannot be subclassed. We prefer decorators over subclasses to allow deployers the ability to change what concrete class they wish to use. Decorators circumvent the restriction that a class can only have one parent.
The approach is to implement the interface of the target class, taking a concrete object of that class to pass calls to, and adding our own logic around those calls. For example, let’s say we would like to log all request times to a particular class. We would create a “timing” class that implements the interface of said class, and takes an instance of the class as the target. Every method in the interface will call the same method in the target instance. For each method for which we want to log the time to perform the method call to the target, we simply capture the time before and after the call. We do some quick subtraction and log the difference.
Again, the advantage over subclassing is that Decorators do not need to subclass a specific, concrete class. The deployer might select a different or even custom class. The Decorator class could still be used whereas a subclass could not.
A related advantage is that the class is easy to maintain and submit to the official uPortal repo because it is only dependent on the interface it implements. This is a significant advantage that is often times overlooked. Small, concise pull requests have a much greater chance of being accepted and merged. The process is also faster when reviewers can quickly understand the code without having to worry about the implications of a subclass.
Decorators are great for adding a responsibility, like logging, but what about actually changing the behavior of a class to meet a deployer’s needs? For that we can use Strategy.
Strategy Pattern
Strategy is a pattern that encapsulates more than one implementation of some feature and makes them interchangeable. How do we leverage this pattern? The book gives an excellent example. Given a class with a method that takes a stream of text and adds line breaks, there are many ways to determine those breaks. This could be hardcoded in a simple fashion, or it could be written to take a line break strategy implementation and apply it to the stream. This externalizes the approach so that it could be configured by a deployer. One implementation could simply break at a certain length. Another one may support hyphenating words. Yet another may look at a larger scale, say paragraphs, to determine the break points.
Implementing Strategy for a feature is a bit more complicated than Decorator. With Strategy, we must consider what context is needed -- what this code needs to know -- to fulfill various Strategy implementations. The context will need to be well understood and open to refactoring if later Strategy implementations have new context requirements.
Fortunately for us, tools like IntelliJ directly support refactoring code into a Strategy implementation. Such tools will often get you 90% of the way to a Strategy implementation. There will likely be a need to pull the new class out into its own file and make some name changes for clarity -- steps the tool may not directly support. Still, it is fairly easy to grab some arbitrary code and apply the Strategy pattern to it.
In our next post, we will walk through some real-world examples where I use these patterns to add customizations to uPortal.
Stay tuned, and contact us with any questions!
Useful Reading:
- Customizing uPortal (Part 1): Using Design Patterns
- Customizing uPortal (Part 2): Using the Decorator Design Pattern
- Customizing uPortal (Part 3): Using the Strategy Design Pattern
- Customizing uPortal (Part 4): Adding Custom Code to uPortal-start