Consider a domain, for example an online bookshop project that we call BuyCheapBooks. The Ubiquitous Language for this domain would talk about Book, Category, Popularity, ShoppingCart etc.
From scratch, coding this domain can be quite fast, and we can play with the fully unit-tested domain layer quickly. However if we want to ship, we will have to spend several times more effort because of all the extra cross-cutting concerns we must deal with: persistence, user preferences, transactions, concurrency and logging (see non-functional requirements). They are not part of the domain, but developers often spend a large amount of their time on them, and by the way, middleware and Java EE almost exclusively focus on these concerns through JPA, JTA, JMX and many others.
On first approximation, our application is made of a domain and of several cross-cutting concerns. However, when it is time to implement the cross-cutting concerns, they each become the core domain -a technical one- of another dedicated project in its own right. These technical projects are managed by someone else, somewhere not in your team, and you would usually use these specific technical projects to address your cross-cutting concerns, rather than doing it yourself from scratch with code.
Logging is the core domain of Log4j, and it must itself deal with cross-cutting concerns such as configuration.
In this perspective, the cross-cutting concerns of a project are the core domains of other satellite projects, which focus on technical domains.
Hence we see that the very idea of core domain Vs. cross-cutting concerns is essentially relative to the project considered.
Note, for the sake of it, that there may even be cycles between the core domains and the required cross-cutting concerns of several projects. For example there is a cycle between a (hypothetical) project Conf4J that focuses on configuration (its core domain) and that requires logging (as a cross-cutting concern), and another project Log4J that focuses on logging (its core domain) and that requires configuration (as a cross-cutting concern).
There is no clear and definite answer as to whether a concept is part of the domain or whether it is just a cross-cutting concern: it depends on the purpose of the project. There is almost always a project which domain addresses the cross-cutting concern of another.
For projects that target end-users, we usually tend to reuse the code that deals with cross-cutting concerns through middleware and APIs, in order to focus on the usually business-oriented domain, the one that our users care about. But when our end-users are developers, the domain may well be technical.
Small details matter because you deal with them often. Any enhancement you make thus yields a benefit often, hence a bigger overall benefit. In other words: invest small care, get big return. This is an irresistible proposal!
Examples of small design-level details that I care about because I have experienced great payback from them:
All these details emphasize that code is written once then used many times. The extra care at time of writing pays back at time of using, each time, again and again. Each enhancement that minimises brain effort at time of use is welcome, because software design is a matter of economy.
Other kinds of “details” that I care about involve the human aspects of crafting software: being on site, face-to-face communication rather than electronic media, respect and consideration at all times, always celebrate achievements, etc. Because ultimately, it also boils down to people that feel like building something together.
Deciding where and how to place the annotations is not innocent. The last thing we want is to create extra maintenance effort because of the annotations. In other words, we want annotations that are stable, or that change for the same reasons and at the same time than the elements they annotate. This article suggests some good practices on how to design annotations.
Annotations are location-based
Language annotations or even good-old xDoclet tags enable to augment program elements with additional semantics, which can be used to configure tools, frameworks or containers.
Configuration is now increasingly done through annotations spread all over the project elements. The key advantage is that the location of the annotation directly references the program element (interface, class etc.), as opposed to configuration files that must reference program elements using awkward and error-prone qualified names: “com.mycompany.somepackage.MyClass”, that are also fragile to refactoring.
For example, we can annotate an entity to declare how it must be persisted, we can annotate a class to declare how it must be instantiated by a Dependency Injection framework, and we can annotate test methods to declare their purpose.
If not placed and thought carefully, annotations can make your code harder to maintain. This happens when annotations are placed at the “wrong” place, or when they introduce undesirable coupling, as we will see.
Dependencies still matter
The question of coupling between elements of the code base is also relevant for annotations. That the coupling is done via an annotation rather than plain code does not make it more acceptable.
We want to group together things that change together. As a consequence, put your annotations on the elements that change with the annotations.
In particular, when the annotation is used to declare a dependency:
Only annotate the dependent element, not the element depended on
If you use Dependency Injection and you want the class MyServiceImpl to be injected everywhere the interface MyService is used, then Guice offers the annotation @ImplementedBy:
This annotation is a direct violation of the advice above, since it makes a pure abstraction (an interface) aware of an implementation, whereas the regular dependency should be the other way round: only the implementation must depend on the interface.
I must however acknowledge that the annotation @ImplementedBy is quite convenient for unit tests anyway, to declare a default implementation for the interface. And it was done just for that, as described in the Guice documentation along with a warning:
Use @ImplementedBy carefully; it adds a compile-time dependency from the interface to its implementation.
Favor intrinsic annotations
If you want to declare that a service is stateless, you cannot get it wrong: just put the annotation @Stateless on its interface. This is straightforward because being stateless is a truly intrinsic property. It also makes perfect sense to annotate a method argument with the @Nullable annotation, as the capability to expect null or not is really intrinsic to the method.
On the other hand, a service interface does not really care about how it is called. It can be called by another object (local call) or remotely, through some remote proxy. The object is not intrinsically local or remote in itself.
The point is that the decision to consume the service locally or remotely does not belong to the service, in itself, but depends on each client. It actually belongs to each use-case considered.
Said another way, specifying @Remotable or @Local directly on the service would require the developer of the service to guess how it will be used!
Intrinsic properties really belong to the element and therefore are stable, as opposed to use-case-specific properties that vary with the particular case of use. Hence, if we want stable annotations:
Only annotate an element about its intrinsic properties, and avoid annotating about use-case-specific properties.
Annotations as pointcuts
Let’s consider an example of an accounting service in a bank. Only selected categories of staff can access this service. We can use annotations to declare its security configuration:
The problem with that approach is that it couples the service directly to the user roles defined elsewhere; as a consequence, if we want to change the user roles (we now need to add the user role “externalauditor”), we will have to review every security annotation and change them. On the other hand, if we want to change the access policy (which happen every time a new senior management comes into place), we will also have to change annotations all over the code. How can we improve that?
We can improve the situation by going back to the business analysis on the topic and separate what’s intrinsic and what’s not. In other words, we want to find out how did a BA came up with the security roles for the service.
Rather than specifying the need for security in terms of allowed user roles, we can instead declare the facts: the service is “sensitive” and is about “accounting”:
Then we can define expressions that use the declared annotations (which are now stable because they are intrinsic) to select elements (here services) and associate them to allowed user roles. These rules should be defined outside of the elements they apply to, typically in configuration files.
Thanks to the annotations that already define half of the security knowledge, expressing the rules becomes much simpler that doing it method by method. So next time the senior management changes and decides that from now on, “every service that is both Confidentiality(Sensitive) and Domain(Accounting) is only allowed to corporate-officer roles”, you just have to update a few rules expressed in terms of domain and confidentiality level, rather than by listing many method.
The mindset is very similar to AOP where we first define pointcuts, and then attach advices to them. Here we use annotations as an alternative way to declare the pointcuts.
Annotations are very efficient to declare properties about program elements directly on the elements. They are robust versus refactoring and are easier to use than specifying long qualified names in XML files.
To get the best of annotations, we still need to consider the coupling they can introduce, in particular with respect to dependencies. If a class should not know about another, its annotations should not either.
Annotations are much more stable (less likely to change) when they only relate to intrinsic properties of the elements they are located on. When we need to configure cross-cutting concerns (security, transactions etc.) annotations can be used to declare the half of the knowledge that is really intrinsic to the elements, in the same spirit than pointcuts in AOP.
All that leads to the acknowledgement that even though annotations can be of huge value, in practice there is still a case for configuration files to complement them. In this approach, annotations on elements declare what belongs to the elements, while each use-case-specific configuration file makes use of the annotations and as a result is much simpler.
The concept of degrees of freedom looks so relevant to software development that I am wondering why it is not considered more often. Fortunately Michael L. Perry dedicates a full section of his blog to that concept. In this post I will quote a lot, please consider that as a sign of enthusiasm.
A mathematical model of a problem is written with equations and unknowns. You can think of the number of unknowns as representing the number of dimensions in the problem.
Depending on the number m of equations compared to the number n of unknowns (variables), there are several cases:
m > n: there is no solution, the problem is over-constrained
m = n: there is only one solution
m < n: the system is under-determined system, and the dimension of the solution set is usually equal to n ? m, where n is the number of variables and m is the number of equations.
A common concept in mechanics
The concept of DOF is prevalent in mechanics. In particular, a system with more internal constraints than the total possible number of DOFs has no solution. However in practice it can still work, provided some of the bodies are not absolutely rigid.
The table is often given as an example, because it only needs three legs to be stable on the ground, but usually has 4 legs. This only works because the table is not fully rigid, and can accommodate the small imperfection of the ground.
Not yet common in software
In his post, Michael L. Perry explains in practice how to analyse software using DOF. First find the unknowns:
To identify the degrees of freedom in software, start by defining the unknowns. These are usually pretty simple to spot. These are the things that can change. In a checkbook program, for example, each transaction amount is an unknown, as are the the account balance and the color used to display it (black or red).
Then find out the constraints between the DOFs.
Next, define the equations. These are the relationships between the unknowns that the software has to enforce. In the checkbook, the balance is the sum of all transaction amounts. And the color is red if the balance is negative or black otherwise.
Subtract to find your degrees of freedom. One amount per transaction (n), one balance, and one color gives n+2 unknowns. The balance sum and the color rule give us two equations. n+2-2 = n degrees of freedom, one per transaction.
Quoting again Michael L. Perry (across various posts in the DOF category):
Understanding the degrees of freedom in the software helps to create a maintainable design.
Adding independent data to a system increases its degrees of freedom. Adding dependent data does not. Adding an immutable field does not.
You want no more degrees of freedom in the system than the problem calls for.
The concept of degree of freedom is remarkably useful to help distilling the domain down to the essential variable parts and the constraints between them. Any extra independent data can only create opportunities for bugs.
For tools to be aware of patterns, the patterns must be formalized, at least partially. At this point I must quote Gregor Hohpe to clarify my thoughts, as I strongly agree with his skipticism:
Typically, when people ask me about “codifying” or “toolifying” patterns my first reaction is one of skepticism. Patterns are meant to be a human-to-human communication mechanism, not a human-to-machine mechanism. After all, I have pointed many people to the fact that a pattern is not just the piece of code in the example section. It’s the context-problem-forces-solution combination that makes patterns so useful.
Patterns link together a problem part to a solution part. This is expressed within the limits a stated context outside of which it is no more applicable. Patterns also emphasize the forces involved, that you must consider to decide how and whether or not to apply the pattern.
Patterns litterature usually describes examples of application of patterns. In your project, you will have to do more work to adapt the pattern solution to your exact need. A pattern solution may be stretched a lot, but the pattern remains as long as humans still recognize its presence. Every different way of applying a pattern is called a pattern variant.
Addressing the variant problem
Formal descriptions of anything human is too restrictive, and this is especially true for patterns which are the product of human analysis, in that they resist simple formalization. However if we focus on sub-parts of patterns, it becomes easier to formalize them, at least for their solution part.
For example a design pattern that uses (in its solution part) some form of inheritance admits several variants. At first, the pattern solution seems to resist against its formalization. However if we now focus on the inheritance part only, we can enumerate every possible alternative for it. For example we can use:
interface and classes that implement it
abstract class and classes that extend it
concrete class provided it is not final (assume we’re in Java), so that it can be extended
Notice that each alternative is a solution to the same problem “How to realize some
form of inheritance”. We can say that each alternative is indeed a pattern, and by the way Mark Grand already described them in Patterns in Java Volume 1. These patterns are easy to formalize, as they can be precisely described in terms of programming language elements.
How can we split a pattern into parts? The idea is to identify the areas that are fragile with respect to the variant problem in the solution part of a pattern, and to consider them as lower-level problems embedded inside the bigger pattern.
In the example before, the problem was to achieve “some form of inheritance”, and we listed three patterns that address this problem.
Provided it can be split into sub-parts (hence into smaller problems), any pattern solution can be formalized by recursively formalizing its sub-parts. If a sub-part cannot be easily made formal, then it can be split again into sub-parts, and so on until each sub-part can.
Given a pattern solution that we want to formalize:
If it can be described formally directly, then we are done (terminal)
If it cannot be fully described formally, then extract the problematic sub-parts into sub-problems, then find every pattern that addresses each sub-problem, and formalize their solution part
We can then represent a pattern as a tree of smaller patterns, where the solution part of each patter is connected to the problem part of another pattern.
From pattern language to pattern grammar
When the pattern is applied into the code, at each node in the tree there is actually a selection of which variant of the sub pattern to use. As such, each selected pattern represents an atom of decision in the design process.
It then appears that we have a form of grammar for patterns, where there are terminal patterns solutions T (easier to formalize in term of programming language elements), non-terminal parts of patterns solutions N (that cannot be easily formalized but that can formally ask for help to solve their sub-problems), and where the production rules P are nothing but the patterns themselves:
Patterns are production rules that link:
elements of N (the problems) to elements of (N Union T)
I have suggested quickly a way to formalize patterns solutions in spite of their fragility with respect to their variants. This approach identifies patterns as production rules in some grammar over the set of patterns considered.
This perspective is well-suited for tools to work on patterns in real-world projects, where the patterns are indeed applied in many variant forms. The problem of this approach is that every known pattern that is variant-fragile must be reconsidered and have its solution split into a formal part and one or several sub problems to be addressed by specific, lower-level patterns, which themselves must be formalised in the same way.
It is essential that for every problem (“intent”) we can enumerate every pattern that addresses it. Intents can be also classified as a taxonomy, where some intents are specialized versions of more generic intents.
This approach does not claim to formalize the full potential of patterns, it only aims at enabling tools to understand patterns that are already there so that to assist the developers for various tasks.
Over time, patterns have appeared on many different topics, not all related to programming. Here is a list of patterns and pointers to other lists of patterns, to illustrate two things:
Knowledge and experience in general can be packaged into patterns, often using the pattern form. Patterns are convenient for reuse, in any domain.
There are already plenty of patterns, and they cover a really wide range of situations. Given the number of patterns available today, whatever your problem, you will likely find helpful patterns for it.
Though this listing is far from extensive (there are many independently published articles on patterns everywhere), it shows that the pattern community has been quite active to mine as many patterns it could, and this knowledge has been carefully documented in the pattern form, ready for reuse in your next projects.
Of course you don’t need to know them all, not even read them all in advance. However it does help to be aware that for most problems, there probably already exists a few patterns that can prove helpful, and they are only an Internet search away from your needs!
In nature, out of every possible arrangement of several elements, only a few arrangements are stable. This is illustrated with atoms combined together, or smaller particles arranged together into atoms, where not every combination is sustainable.
Unstable arrangements tend to move toward stable ones over time. Whenever you observe the elements, you mostly see stable arrangements of them. Because there is only a relatively small number of stable arrangements, a brain can be trained to recognize them, and they can even be named and incorporated into the language.
Better with a brain
The capability to recognize common arrangements of elements is beneficial because it saves a lot of time and thinking. Rather than describing in the details each arrangement each time, it is therefore very economical -cheaper- to describe each stable arrangement once, and then to declare that such arrangement happens in this or that case. Saying: “this is an equilateral triangle” is times more efficient than explaining what it is: “there are 3 lines of the same length, each connected to the two others such as they form a closed path, etc.” It also enables thinking at a higher level.
In software development
In software development, the usual elements are classes, interfaces, methods, fields, associations (implementation, delegation, instantiation) and various constraints between them. Given a few of these elements we can form many possible arrangements, however only a relatively small number of them is useful and of interest. This happens because the useless arrangements tend to be quickly replaced by the skill-full developer into other that are more useful. For example, any arrangement of two distinct classes that depend to each other, forming a cycle, is usually not desirable. Patterns authors have been working for almost two decades to inventory as many useful arrangements as they could find, resulting into many books and publications in every domain.
Common stable arrangements of methods, fields and how they relate with each other within one class are simply stereotypes of classes, which we tend to call patterns anyway like the ValueObject pattern in the Domain Driven Design book.
Note that in this discussion we are focusing on arrangements of programming elements in the solution space, not in the problem space, but pattern express intents too.
Harnessing stable arrangements of things: toward more efficient tools
I believe that making explicit the use of predefined common stable arrangements of programming elements, in the coding process, can boost the efficiency of many tools. I also believe that such common and stable arrangements of programming elements have already been identified and are already well-documented in the existing pattern literature.
Rather than configuring tools at the programming element level (class, field, method etc.), if the code is augmented with explicit declarations of the patterns used, the tools can then be configured at the pattern level. For each tool, the idea is to prepare in advance how to configure it for each supported pattern. This preparation must be automated, so that given an occurrence of a known pattern in the code base, the configuration of the tool can be automatically derived from the particular details of the pattern occurrence.
To start with an example, let us consider the pattern Abstract Factory, that defines an Abstract Factory interface and one or more Product interface(s). Then assume that in our code base we have an occurrence of this pattern, where the interface WidgetFactory participates as the Abstract Factory, and the interfaces Window and Button participate as Product. Concrete classes form two families, one Linux family (LinuxWidgetFactory, LinuxWindow, LinuxButton) and one Mac family (MacWidgetFactory, MacWindow, MacButton), where each concrete class participates as ConcreteFactory, ConcreteProduct and ConcreteProduct respectively.
//Given a pattern occurrence from the actual base: occ
//Factory interface knows about the Product interface, not the other way round
For each Product in occ, add constraint (Product, Must not depend upon, AbstractFactory)
//ConcreteProduct must not know about the AbstractFactory
For each ConcreteProduct participant in occ, add constraint (ConcreteProduct, Must not depend upon, AbstractFactory)
//ConcreteProduct must not know about the ConcreteFactory
For each ConcreteProduct participant in occ, add constraint (ConcreteProduct, Must not depend upon, ConcreteFactory)
//Interfaces must not depend upon their implementor
For each abstract participant in occ, add constraint (participant, Must not depend upon, implementor of participant)
I have expanded the auto-configuration script to highlight how we can do more sophisticated configuration as soon as it is supposed to be reused many times, something we would never afford for one-shot configuration. Also in the above script, it is quite obvious that we can extract more powerful primitives to simplify the declaration of the script itself.
The auto-configuration(AbstractFactory pattern, IoC container) could be programmed like the following:
//Given a pattern occurrence from the actual base: occ
//Given the selected family (Mac or Linux) we want to inject: F
//Bind the ConcreteFactory from F to the AbstractFactory interface
For the AbstractFactory participant in occ, and for the ConcreteFactory in occ that belongs to F, bind (ConcreteFactory, AbstractFactory)
//Bind each ConcreteProduct from F to the Product interface
For each ConcreteProduct in occ that belongs to F, bind (ConcreteProduct, corresponding Product)
Again we can see that we can automate the binding of each implementation to its corresponding interface from the single explicit selection of the family F we want to inject. This is a great way to factor out dependency injection to make it more manageable. In particular, the configuration is closer to the way we actually think.
The same idea can be applied for many other tools, in particular:
In this post I described how it makes sense to consider overall arrangements of programming elements as higher-level constructs, which I identify with patterns (the solution part of patterns in fact). I emphasize the fact that the useful arrangements are not that numerous, and that many of them are already documented in the pattern literature. Finally I present how such arrangements or patterns, if declared explicitly in the code, can be leveraged to automate tools configuration in a powerful way.
In my very first job, I was doing R&D, working on a map-matching algorithm. The goal of this algorithm was to pinpoint a moving car on a vector map, based on the data from various sensors, including a GPS, an electronic compass and the car odometer. Such algorithm was essential for the business of the company, and there was very little literature on the subject.
The R&D challenge
At school I had been taught some C programming, so I started doing the algorithm in plain C code. One special case after another, the code began to grow until it became quite complicated. I had a specially equipped car with a computer and all the gear in it to do real testing on the roads around the office, from the highway to forest road, city streets or even car parks, and this was fun! But situation after situation, I had to make the code more and more complicated. At some point, it became obvious to me that the mode of implementation (plain C code) had become the main obstacle for improving the algorithm. It was becoming increasingly difficult to grow the sophistication in a mess of structured code.
My early mentors
At the same time I was willing to progress, so I was getting closer to the few very experienced colleagues. Our company was a startup in 2000, and there were many more junior developers than senior ones. At first, I thought UML could help me (it did not indeed) so I started asking questions about UML. When I became more comfortable with UML, a senior colleague told me I should now have a look at design patterns, starting with the Composite. So I took the GoF book on my desk and began to look at it as a reference to get design ideas during the day. I also borrowed the pattern pattern book from Mark Grand and read it in the train.
And then it has been “Wow patterns are a great way of transfering knowledge!”. I remember reading the pattern “Cache” in the book. It was not in itself a very innovative design idea, but I understood that the pattern format was ideal to document just any idea. I hate long explanations in long books, and the pattern format, which tends to be short and structured, is perfect for quick scanning whenever you’re looking for ideas. Even when I didn’t find a pattern for my case, I found it stimulating to read other people ideas.
Enthusiasm and success as a result
I started to apply the State and the Strategy patterns into the map-matching algorithm and this made it much, much simpler. It actually made it so much simpler that we were now able (the team was growing at that time) to go an order of magnitude further in sophistication, while being perfectly in control of the code. The introduction of two simple design patterns had suddenly given a really big advantage to a piece of code essential to the company! This is how I became enthusiastic about patterns.
What actually happened is that reading and starting to play with patterns just taught me object-oriented programming. Patterns acted like examples of good design, until the underlying principles became natural. Later I discovered the SOLID principles of Robert C. Martin, and recognized the principles behind many design patterns. In my next job experiences I took the habit to look for patterns for whatever problem I was encountering, and to my surprise, I found out that most common problems were already being taken care of in the form of analysis patterns or other kinds of patterns! To give the most obvious example, Martin Fowler “Things that change with time” is really a must-read, which you can apply easily to solve your problem.
This is how I became enthusiastic about pattern, not just design patterns but every kind of patterns, from analysis patterns to domain driven design patterns, enterprise integration patterns, PLoP patterns and many patterns from various authors. I know my enthusiasm is a bit exaggerated, a bit like the souvenir of a first love that cannot be much objective. Fortunately I quickly learnt when not to use patterns, to keep things as possible as they can be, and to do unit testing. By the way the benefits of unit tests also struck me when I started with them, but not as much as patterns, there can be only one first time, and my first first time was with patterns, not unit tests!
There is great power in being able to manipulate collective things as one single thing. It gives you simplicity, hence control. You can focus your attention on it and reason about it, even though behind the hood it is made of many parts. The composite thing is kept simple, therefore you can also deal with several of them at a time. This would not be possible if you had to deal with every part they are made of, because it would be overwhelming.
There exists many strategies to deal with collective things as if they were one single thing: statistics, multiple selection, groups, classifications and super-signs.
Statistics is probably the most obvious way to deal with collective things, when the things can be expressed as numbers. Historically it has been used with great results in physics, thermodynamics in particular.
It is all about extracting a few macro properties that we can reason on instead of the whole set of data:
number of elements
mean, deviation, moments, percentiles, etc.
total property: total weight, total volume, total price
Many software applications enable you to select multiple elements at a time in order to apply one operation to each element:
When sending an email, you can select multiple addresses to send to
In a word processor, you can select several words, several paragraphs, or even all the document to copy, paste or apply formatting to each element
In a spreadsheet, you can select multiple rows or columns to apply operations to, and you can also repeat formula for each row or column
The selected elements can be of the same kind or not. However for multiple selection to be useful, they must share at least something in common: the capability of being copied or pasted, or the fact that they are specific for a particular user.
When multiple selections are often needed, you can create groups. We can consider a group to be a multiple selection made explicit. You create a group and you explicitly add elements to it. Common examples of groups:
Mailing lists are named groups of email addresses
Vectors in maths
As for multiple selection, the elements in a group must share something in common. For example, they must all have a price. Elements of various kinds can be grouped if they relate to something common, for example the set of various data (name, address, phone number, preferred colour and date of birth) specific for a user is called a user profile.
A group is extensional. The elements in the group may or may not know they belong to a group.
Java packages are groups, and they are declared within the same file as the elements they refer to. Java classes also group fields and methods under one name.
The Composite pattern suggests to group objects that share the same interface into a Composite that also shares the same interface. The intent is to manipulate the collective set of objects as if it was one single object, i.e. without knowing it is collective.
You get control over multiple things if you just classify them. Given several flowers, if you classify them into categories, then you can talk about several flowers collectively without having to enumerate each of them: the category is a way to refer to several flowers with just one name.
Classifications enable intensional grouping. This means that groups are defined not by the set of their elements, but by a condition (predicate) to be satisfied. The condition can test for the category of something (is this animal a bird?), or test for its attribute (is this car red?).
Of course abstraction is one particular way to classify.
Java modifiers (private, public, abstract, interface etc.) classify Java elements, and can be used to refer collectively to them, as in “let’s generate the Javadoc for every public elements”.
There are elements that exhibit a special property when considered together as a whole. For example, the ink dots on the paper can be seen as letters. Letters next to each other can be seen together as words, which again can be seen together as sentences, and then again up to the novel. Collective arrangements of multiple things that together exhibit a property are called super signs.
This phenomenon is related to emergence, and only exists for a given observer if he can recognize the super sign.
In science in general, we use models to account for the collective behavior of several elements, typically objects with measurable properties, and forces in action.
In a Java program, idioms and patterns can be considered super signs for those who know them.
Manipulating multiple things in a simple way really matters, it is a life saver.
In software development it is paramount because it is a lever you use to manage tons of data with no effort. The art is to find the way you think about collective things that reduces the most your effort.
Patterns represent a couple (intent, solution); sometime they refer to a solution, more often they essentially represents an intent, independently of its solution.
Sometimes the solution part of patterns includes a trick or a workaround to overcome the limits of a language, but patterns cannot be reduced to that trick. Indeed, a very important role of patterns (not only design patterns but patterns in general ) is that they represent stereotypes of intents.
A matter of intent
Therefore, it does not really matter if the Strategy pattern can be expressed using a Java interface, a C++ functor or a first-class function: it remains a Strategy because this is just what we want: “Strategy lets the algorithm vary independently from clients that use it“.
Another similar pattern is the Command pattern, which intent is:” Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.” Here the intent talks about ‘object’ because it was written for an object-oriented context, however it can easily be made generic if you think ‘handle on function’ (or closure etc.) instead of object. Again, even if first class functions such as delegates in C# can achieve this goal, they do not replace the need to declare the precise intent: “you want to parameterize clients with different requests, queue or log requests, and support undoable operations.” So in some sense, just using a functor without declaring that the intent is to do a Strategy or a Command is like using untyped variables: you are supposed to know what you are doing, but it is implicit.*
Yet another example with the Visitor pattern and its intent: “Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.” This is typically achieved through double-dispatch in languages lacking multimethods, but regardless of how it is implemented the intent remains, and this is what matters most.
Generic Vs. specialized intents
For example, the intent of the generic Proxy pattern defined in a paper from James Noble:
The Proxy pattern is used to “Provide a surrogate or placeholder for another object the Subject to control access to it”. A Proxy object provides the same interface as the original Subject object, but intercepts any messages directed to the Subject. A Proxy object can therefore be used in place of the Subject by a client which is designed to access the Subject, without the client being aware the Subject has been replaced by a Proxy.
This intent can then be specialized for various purposes, leading to several specialized patterns:
Remote Proxy: provides a local representative to an object that is only available on a remote machine
Protection Proxy: checks the access rights before directing to the original object
Virtual Proxy: creates expensive object only on demand so that they are created only when necessary
Cache Proxy: an object representative that remembers the result of calling the methods of an object to avoid directing subsequent call again to this object
Counter Proxy (smart pointer management), etc.
We say that these patterns are specializations of the Proxy pattern. The main Proxy pattern introduces the common solution—providing a placeholder for an object. Every specialized proxy pattern is a special kind of Proxy: A “Protection Proxy” is a special kind of “Proxy”. The specialization here only deals with the Intent part of the patterns.
Now that functional languages are getting more attention, it becomes fashionable to question the usefulness of patterns: “Scala does that without the need for patterns”. I agree Scala is great, I disagree this argument. Patterns are first of all signs to denote intents, even if they can do more.
Patterns as Signs, James Noble and Robert Biddle, Victoria University of Wellington, New Zealand
Classifying Relationships Between Object-Oriented Design Patterns, James Noble, Microsoft Research Institute, Macquarie University
* By the way, how to achieve “undoable operations” by using first class functions in an elegant way? this would require passing two functions always together: do() and undo()