8

Now You Know Some Patterns, What Next?

Over the course of writing this book, I asked a few friends, colleagues, and at least one of my many archenemies for input. Invariably, they would ask about a pattern they had studied in school or used on a project wondering why it wasn’t included in this wonderful tome. The short answer to their question: the goal of the book is to focus on patterns that you can add quickly to your coding arsenal. Patterns that have a quick return on your investment of time and money.

Many of the patterns I chose to omit are very similar to the ones I included in this book. The patterns that made the cut were strictly my own preferences. These patterns have proven the most useful to me during my 25 years of experience as a celebrated and award-winning software engineer using C#.

By the end of this chapter, we will have covered the following topics:

  • The GoF patterns we didn’t cover will be discussed very briefly.
  • Patterns outside of object-oriented programming (OOP). There are patterns that are not applicable to OOP—for example, patterns designed to describe database or network structure.
  • How to create your own patterns. The GoF book documents a format for creating your own patterns.

Please note that I am only using diagrams. There is no code for this chapter. Likewise, there aren’t any technical requirements.

Patterns we didn’t discuss

I didn’t cover all 23 patterns in the GoF book. I only covered about half. A number of factors went into deciding what to include and what to leave for a tacit discussion in this chapter. Some of the patterns are more troublesome than they are worth. The Memento pattern solves a problem that can easily be solved with a few .NET features. Some patterns were not included because they are very similar to another pattern we covered. Some are patterns you are never likely to need. The Interpreter pattern is only useful if you are inventing a new programming language. This is rarely done anymore owing to the popularity of domain-specific languages (DSLs). Tools exist for the construction of DSLs that preclude the need for the Interpreter pattern.

Here are the patterns from the original GoF book we didn’t cover in this book:

  • Prototype
  • Adapter
  • Flyweight
  • Chain of Responsibility
  • Proxy
  • Interpreter
  • Mediator
  • Memento
  • State
  • Template Method
  • Visitor

I’ll discuss each of these patterns at a high level in the following sections.

Prototype

Making copies of objects can be tricky but if your object is flat, with no composition and just a few fields, it’s no problem at all. However, making a deep copy of a complicated object that was built using composition and inheritance—and it’s got a few layers of each—is harder. Deep copying refers to making an exact copy of an object from the highest level to the lowest level. The first step is to instantiate a new object from the same class. Then, you need to copy the values from all the properties and fields into the new object. You can only do this if every field or property in the object you want to copy is public. This includes all the objects used to compose the object you’re copying. Even if you manage to do this, the copy you create is subsequently of the dependent class used to make the copy.

Let’s look at it another way. You’re beginning a project with the Transylvanian Historical Society. Your job is to copy, brick by brick, a castle owned by one Vlad von Dracula. The castle’s copy will be a museum in another town. There’s a catch: the castle’s drawbridge is up and you’re not allowed inside.

It isn’t hard to make a copy of the outside parts of the castle you can see, but it is impossible to duplicate the interior—especially that creepy crypt in the basement. That is, unless you had inside help. A man named Renfield offers his help. He has full knowledge of the castle’s interior because he never leaves its walls. If you can get Renfield to help you from the inside, making a copy of the castle isn’t going to be difficult.

Prototype is a Creational pattern that enables you to copy an object brick by brick. It works by delegating the copy job to the object itself. In short, it’s an inside job. The copy operation itself is called cloning. An object capable of cloning itself is called a prototype.

Have a look at the following diagram:

Figure 8.1: The Prototype pattern is used when you want to be able to make deep copies of an object.

Figure 8.1: The Prototype pattern is used when you want to be able to make deep copies of an object.

Let’s review the Prototype pattern diagram by the numbers displayed on it, as follows:

  1. The Prototype interface defines the method that will perform the cloning operation.
  2. The ConcretePrototype implements the Prototype interface and provides the implementation of the Clone method. If your object structure is complicated, this method is positioned to be able to see everything inside the object where it lives. This method might be refactored so that its name is Renfield. He’s the guy inside the castle who can help you clone it. The Clone method likewise can provide you with details needed to copy the object without losing any blood.

The Prototype pattern can be very useful if you need a bunch of classes copied from a small set of objects defined by concrete subclasses. Think back to our bicycle factory. Let’s say there are half a dozen bicycle configurations that are very popular. Let’s further presume that Phoebe’s robots need a new instance of the bicycle object in order to manufacture a physical bicycle. In this case, a bicycle object is instantiated, the bicycle is manufactured, and the object is destroyed.

It would make sense in these circumstances to make a collection of “master copies” of the popular bicycle models and configurations. These masters could be cloned instead of the software needing to run an expensive builder method to generate a new bicycle conforming to a commonly ordered configuration.

Adapter

I keep two sets of wrenches in the back of my Jeep. One set is a common 3/8” mechanical ratchet set. One time I was driving home through Oklahoma from vacation in the Ozark mountain range. We had opted to take a scenic route by driving the back roads through a forested area of the state. We were cruising along, and I ran over a board in the middle of the road. It was unavoidable. I immediately switched my car’s display to the tires, and over the course of a few miles, I could see the pressure in one tire gradually decreasing. I had a nail in one of my tires. I pulled over at the first opportunity and found some level ground so that I could safely jack my car and change the tire.

My wife and two girls were with me. We had to take all the luggage out of the car to get to the jack. If you’ve ever tried to change a tire with factory-supplied tools, then you understand I was hot, frustrated, and stuck on the side of the road. A pickup truck pulled up behind us, and the driver, wearing a cowboy hat, jeans, and a t-shirt, offered to help us out. I could see an array of expensive power tools in the back of his truck. I accepted his offer. We loosened the tire’s lug nuts with his power wrench, and in minutes, we were back on the road. I vowed the first thing I’d do when I got home was to get me the same kit he had.

The minute I brought my new power wrench home, I wanted to use it on more than just changing a tire. After all, flat tires don’t happen all the time. I wanted to use it with my other ratchet set. Unfortunately, the 3/8” sockets from my other set don’t work with the ½” power tool. If you’re not familiar with these tools, check out the following screenshot:

Figure 8.2:  My power impact wrench can only use the socket wrench sockets if I use an adapter.

Figure 8.2:  My power impact wrench can only use the socket wrench sockets if I use an adapter.

The power wrench is like a normal wrench, except that the drive square is bigger on the power tool. The drive square is the part of the tool where you attach the sockets, which have a square hole in them. There’s no way a 3/8” socket can fit onto a ½” drive square. That is, it didn’t fit until I found an adapter. An adapter lets me use one interface—such as a 3/8” socket—with a different interface, such as the drive square of a ½” power socket tool.

The Adapter pattern does the same thing for your classes. An adapter implementation allows two classes with different interfaces to be used together. If my wrench problem were expressed in Unified Modeling Language (UML), it might look like this:

Figure 8.3: A class structure following the Adapter pattern is used to allow a class following one interface to work seamlessly with a different interface.

Figure 8.3: A class structure following the Adapter pattern is used to allow a class following one interface to work seamlessly with a different interface.

Let’s break this down, as follows:

  1. The power wrench is the client and we need a way to attach the smaller 3/8” sockets to the ½” drive square. To do this, we need an adapter.
  2. The adapter should be described with an interface to prevent tight coupling. This interface calls for an AttachSocket method, which is implemented in the concrete adapter class.
  3. The concrete adapter class implements the interface and contains a method that accepts something from the client that can be adapted.
  4. The ThreeEighthsInchSocket class represents the incompatible interface you’d like to connect to your client.

In short, the adapter implements the client interface—ISocketWrenchAdapter in this example. It also wraps the class with which we are interfacing—in this case, ThreeEighthsInchSocket. The adapter receives the drive square from the power tool when it calls the AttachSocket method in the adapter class. The internals of this method perform the logical operations needed to convert that input into something the adaptee can use. In software parlance, the client would be making a call to the adapter class. The method called would provide conversion logic that converts what was passed into the method so as to be compatible with the adaptee.

This can be a very useful pattern when you need to leverage third-party or legacy with new work.

Flyweight

Have you ever had a thought that keeps you awake at night? Or maybe you have a thought that wakes you up? You’re sound asleep and then you awake with a jolt. Your half-slumbering brain just figured out what’s wrong with line 37.

Late one night in a fit of heuristic frenzy, Kitty wonders what would happen if one day, demand for Bumble Bikes exploded. What if the small company was inundated with thousands of orders? The robotic manufacturing system Phoebe built instantiates a bicycle object each time it builds a bicycle. Each bicycle object takes up space in the server’s random-access memory (RAM). Kitty decides to try a simulation using a development server. After a few load tests, she determines she can load 1,000 instances of a bicycle object complete with the bridged painter system. Once the object count goes above 1,000 concurrent objects, the server starts to slow down. Once she reaches 2,000 objects in memory, the system nearly grinds to a halt and is unusable.

One obvious solution to this problem is to order more RAM for the server. Of course, this makes it a hardware problem. We are software developers. Maybe there is a way to solve this problem by strictly using software patterns. Maybe a small adjustment will prevent us from having to ask the pointy-haired boss for several thousand dollars in upgrades.

The Flyweight pattern is used to move some of the shared elements of each object’s state into one shared object. Sometimes, you can shift a large amount of data out of memory and into one shared object. You see this anywhere you have a high object count. For example, if you use C# with Unity to develop game software, your game might have hundreds, or even thousands, of enemies. Maybe you’re leveraging a homemade particle system with thousands of shiny moving particles, or maybe you’re doing a factory simulation where thousands of bicycles are being manufactured by a small cadre of robots.

Consider the following diagram:

Figure 8.4: The Flyweight pattern entails shifting duplicated state variables into a separate object that can be shared to reduce memory footprint.

Figure 8.4: The Flyweight pattern entails shifting duplicated state variables into a separate object that can be shared to reduce memory footprint.

Let’s break it down by the numbers in the diagram, as follows:

  1. Here, we have a Bicycle class with all its parts. For our simulation, we’re just going to make road bikes. The ordering system lets customers customize the bicycle frame with a special paint job, or pick from one of the standard colors. The seat can also be swapped out for one of several models. However, the rest of the properties offer no customization options. They will be the same for every road bike we build.
  2. Now, watch what happens if we shift the parts of the state that don’t vary into a separate class. Here, our Bicycle class only contains those properties that might be different between each bicycle we make.
  3. The elements of state that are common for all objects are called the extrinsic state and are represented in the BicycleFlyweightCommonState class. It’s a bit of a mouthful, but I wanted to be sure you recognized which part was the flyweight. We can make one instance of this class and share it with all 1 million of the Bicycle classes in Phoebe’s simulation.

Let’s say the Bicycle class weighs in at 16 kilobytes (KB) of memory usage upon instantiation. If Phoebe wants to generate 1 million of them, that’s going to consume 16 gigabytes (GB) of memory. Once we shift the repeated state to the flyweight object, we chop out 14 KB for one object. Since we only instantiate it once, the memory footprint comprises only the intrinsic state. We instantiate 1 million bicycle objects at 2 KB, or total consumption is 2 GB plus the negligible 14 KB for a single instance of the extrinsic state in the flyweight class. That’s a nearly 8X reduction! I don’t know about you, but I would strut for a week if I could reduce my software’s memory footprint by 8X just by rearranging some classes!

Chain of Responsibility

The success of Bumble Bikes isn’t owed entirely to Kitty’s designs nor Phoebe’s brilliant robotics. Bumble Bikes also has a focus on quality and quality assurance (QA) is completely automated. A system of cameras does a series of inspections using artificial intelligence (AI) via OpenCV. The inspection begins with the frame, then moves to the handlebars, the drivetrain components, the brakes, the wheels, the tires, and finally the seat. The logic used by the AI to perform the inspection is not contained in a single method. That would violate the single-responsibility principle (SRP). Instead, each inspection’s logic is encapsulated within a separate method for each inspection. The inspection happens sequentially, beginning with the frame. If any of the inspections fail, the bicycle is flagged as a defect and set aside for remediation performed by a human bicycle mechanic.

You can see an overview of the QA checks in the following diagram:

Figure 8.5: Sequential quality assurance checks on a bicycle are one example of Chain of Responsibility.

Figure 8.5: Sequential quality assurance checks on a bicycle are one example of Chain of Responsibility.

This sequentially ordered set of inspections uses the Chain of Responsibility pattern. If any of the inspections fail, the remainder of the inspections is not run. This is how Toyota makes cars. If a defect is found on the assembly line, everything stops until the problem is corrected.

The pattern itself takes on the form seen here:

Figure 8.6: The Chain of Responsibility pattern is used anytime you need a stateful sequence of events.

Figure 8.6: The Chain of Responsibility pattern is used anytime you need a stateful sequence of events.

Let’s break it down, as follows:

  1. An interface, usually called IHandler, defines two methods. The Handle method defines the method signature for the implementation of the inspection logic. The Next method allows us to move to the next inspection, assuming the current step passes.
  2. An abstract handler class defines the next handler, which is a common property among the concrete classes.
  3. The concrete classes inherit from Handler and implement their inspection logic with the Handle method, which overrides the base class method. Similarly, the Next method overrides the base and is used to pass the torch on to the next runner or, in our case, pass the current inspection and move to the next.

Flowchart logic has been around since the Turing machine. It should come as no surprise there is a pattern to encapsulate this universal concept.

Proxy

One day, Kitty and Phoebe get a call from Dr. Eloise Swanson. Dr. Swanson has started a new business called U.S. Robots and Mechanical Men. Her company’s newest product is a high-end software development kit (SDK) for robotic control. Dr. Swanson had studied Phoebe’s designs while in graduate school at the Massachusetts Institute of Technology (MIT) and thought Bumble Bikes would make a good partner for beta testing.

The SDK was very effective and easy to use. However, it wasn’t a drop-in replacement for the software Kitty and Phoebe had written. First, there’s no way an SDK can fully replace custom-tailored software for any business. The second problem was bloat because the SDK was designed to work with any robotic system. A great deal of code was used to account for every possibility.

Kitty and Phoebe found that the SDK was a good fit to control their painting robots. However, it was only needed when a custom paint job was ordered that had never done before. Once a paint job was performed, it was cataloged and the color formula could be reused. The SDK from U.S. Robots made much shorter work of the paint jobs, at the cost of slow initialization and a large memory footprint. If the girls were to couple their code to the SDK, which of course they wouldn’t, all their work would be slowed down by an expensive initialization process they would rarely use.

The solution is to lazy load the SDK objects only when they are needed. The P roxy pattern allows you to define and use a placeholder for an object. The proxy could then load the big, slow SDK classes only when they were needed. The rest of the production process would remain unhampered.

Let’s examine the structure of the Proxy pattern in the following diagram:

Figure 8.7: The Proxy pattern can substitute a simple object for a more complicated one until the complicated one is actually needed.

Figure 8.7: The Proxy pattern can substitute a simple object for a more complicated one until the complicated one is actually needed.

Breaking it down will help us understand further, so we’ll do just that:

  1. We have this SDK from a third-party vendor. We know tightly coupling to a third-party vendor’s SDK is a bad idea in the first place. In this case, the SDK has a method we want to use, but its containing class has a big, slow constructor, and the object is rarely used. We need a way to lazily load this object only when it is needed while also preventing a tight coupling.
  2. We create an interface to prevent tight coupling. Our client software can require the interface, and any changes in the future are fine as long as we can keep the interface.
  3. Then, we create a wrapper that holds the SDK instance. The wrapper holds an instance of BigSlowButVeryUsefulPaintService, which might take several minutes to instantiate. That’s a long time, considering we only need one method. Our wrapper instantiates BigSlowButVeryUsefulPaintService, then calls the expensive InitializeSystem method only when it is really needed. Since we bore the instantiation cost, we can store the instance in a private property and use it again if the need arises. That might sound like a singleton, but singletons ensure only one instance is ever created. Here, we are just reusing what we have already made, which isn’t quite the same thing.

Remember, usese the Proxy pattern anytime you need a placeholder for a third-party, legacy, or overly expensive object. This will to prevent tight coupling and defer expensive operations that might happen on instantiation for when they are needed.

Interpreter

The Interpreter pattern is one you will likely never need. This pattern is used when you have a custom language you need to interpret that can be expressed using abstract syntax trees (ASTs). With the rise in popularity of domain-specific languages (DSLs), entire toolkits, such as JetBrains Meta Programming System and Visual Studio Enterprise’s Modeling SDK, make creating a custom language interpreter a relatively simple project.

Since DSLs are well beyond the scope of a book on patterns, I will list a reference to the DSL tools I’ve mentioned here in the Further reading section at the end of the chapter.

Mediator

It isn’t difficult to imagine a future for Bumble Bikes where their automated manufacturing system might expand and become more complex. Right now, we have the Builder pattern controlling the robotics that build bicycles and wheelchairs. There are two physical factories, and each factory specializes in just a few products. All that could change in the future.

I worked for an aircraft manufacturer on a joint venture with another aircraft manufacturer. Our company made the powerplant (engine) and assembled the final aircraft. The partner company built the body of the aircraft. Other partner companies supplied avionics, which are the electronic flight-control systems present in modern aircraft. Another company still manufactures military components that I’m not allowed to talk about.

Imagine if Bumble Bikes had a slew of manufacturing operations like that. Even if they were all collocated in one plant, the level of signal communications between all the different robotic manufacturing systems could become very chaotic. If each system were to communicate with all the other systems directly, we’d very quickly find ourselves in trouble.

If you’ve ever created a large web application using HTML and JavaScript, you have run into a similar problem. You have dozens of different pieces of code modifying the Document Object Model (DOM) in response to potentially hundreds of signals emanating from user interactions, timers, Representational State Transfer (REST) application programming interface (API) calls, third-party SDKs such as jQuery, and third-party advertising sites injecting code into your site for monetization. When a system such as this reaches critical mass in terms of signal complexity, it becomes slow and nearly impossible to debug and maintain.

Let’s consider one more example: a natural disaster such as a hurricane or tornado. What if all the first responders (FRs) were able to contact each other directly? Every firefighter could radio every policeman, who could radio every emergency medical service (EMS) unit. The EMS responders could talk directly to triage nurses and Red Cross volunteers. All this happens on one big open communication channel. What are your odds of surviving a disaster in this environment? Open direct communications can sometimes be a good thing, but I think we all realize it doesn’t scale. Now, imagine hundreds of objects in a piece of software that all have direct access to all the other objects on the stack. You see the problem, right?

What’s needed in all these circumstances is some form of dispatcher: a central hub for communications. The Mediator pattern embodies this role. A mediator acts as a central hub for all communications and routes requests to the objects that need them in a controlled manner. Let’s examine the diagram shown here:

Figure 8.8: The Mediator pattern involves a single centralized object that directs calls between objects.

Figure 8.8: The Mediator pattern involves a single centralized object that directs calls between objects.

By the numbers in the diagram, it works like this:

  1. This is a base class for the colleague objects.
  2. You have a bunch of colleague objects that need a centralized way to communicate. Here, I just have four, which is probably no cause to run to the Mediator pattern. But imagine 400 objects all communicating directly with each other! Like I said before, direct communications don’t scale!
  3. We make an interface called IMediator, which sounds cool when you say it out loud. This, as usual, prevents tight coupling.
  4. Now for the good part. A central object based on the IMediator interface contains instances of all the objects and has defined communications channels between them in the form of methods that I called ReactWithX, where X is the number corresponding with the object sending the signal. ReactWith1 calls an appropriate method on Colleague1, and so on.

This pattern is designed to simplify the communications process, but it usually results in a very large object with lots of internalized component instances and methods for communicating. You have to weigh the complexity of the Mediator class against the benefits of the centralization it offers. On the one hand, as a developer, it’s nice to have one class where you can drop a breakpoint in your IDE. Like a lion scoping out the local watering hole, eventually, all message traffic passes through this one class, making it simpler to find bugs.

On the downside, the class itself can become unwieldy.

Memento

Have you ever saved a game or used undo in your favorite editor? If so, then you’ve likely interacted with the Memento pattern. The best analogy for a memento is a cool but outdated technology. When I was in grade school, the hottest camera was called a Polaroid. Most cameras back then involved a roll of film. You would shoot your pictures on the camera, then take the film to a drug store to be developed. It took about a week to get your pictures back. However, with a Polaroid, you could take your pictures, which were ejected from the camera and self-developed within a few minutes. The development process seemed to go a little faster if you shook the picture, which gave rise to a popular song lyric, and accompanying dance move, “Shake it like a Polaroid picture”.

Back then, we called these pictures snapshots. This term is used today in conjunction with the Memento pattern. A snapshot, like a Polaroid picture, is a representation of an event or place at a particular point in time. So it is with software as well: a snapshot represents the state of an object at a point in time. Like a photo, a software snapshot can be saved as a memento—something to remind you of that one time when your object was in that state.

Text editors such as your IDE or Microsoft Word are constantly tracking the state of your documents and saving mementos as you type. When you hit Ctrl/Command + Z on your keyboard, you can go back sequentially to earlier and earlier mementos.

At first glance, this seems easy. All you’d need to do is create a List<> object to hold, say, the last 100 state changes your user has made. Maybe you store a memento every 30 seconds or so. That’s easy. It’s that easy only if every object in your state and every object used in inheritance and composition has 100% public properties. If the entire state is public, you wouldn’t need a pattern for this.

The real impediment here is the same one we encountered with the Prototype pattern earlier. We were trying to copy Count Dracula’s castle, but we weren’t allowed inside the front gate. Making a shallow copy of the outside of the castle is straightforward, to make a full copy of the whole castle requires an inside actor. This is equally true in the Memento pattern. In the memento’s case, the inside actor is a nested class. The nested class has access to the outer class’s state and can store our suggested List<> object containing our undo history. Let’s look at the diagram shown here:

Figure 8.9: The Memento pattern.

Figure 8.9: The Memento pattern.

Since the most familiar example is a text editor, I used that for the diagram. Let’s review the numbered parts of the diagram, as follows:

  1. This class represents the DocumentEditor class, which is our client. The memento pattern calls this the originator since this is where the stored state comes from.
  2. When the editor needs to save an undo state—or, for that matter, a file—it can use the Memento class for storage. Note that all the internals are private. The Memento class is nested inside the originator; it is defined inside the DocumentEditor class as a nested class. Besides being locked down, you should consider Memento classes immutable: once you create them, you should never change them. The private properties are set via the constructor and subsequently never modified. This means when you implement, there should not be any setter accessor methods for these properties.
  3. There isn’t a standard way to draw internal classes in UML, so I put the Caretaker class outside the dashed box as a visual clue that there’s something different about this class. Remember, Memento is inside the DocumentEditor, and not via composition. The Caretaker class, in turn, contains the DocumentEditor object inside the Originator field. The Caretaker class, then is responsible for creating and restoring mementos, as well as the undo history.

The Memento pattern can be difficult to get right, and you might go a long way in your career without needing it. Even if you land a job at a company that makes a document editor, chances are you’ll be using one of the many excellent third-party user interface (UI) controls, such as those from Telerik, that have already implemented this for you.

The other point worth mentioning is that you can achieve a nearly identical effect using C#’s serialization libraries. Usually, saving the application state also entails persistence. Good editors let you undo the last 100 changes. Great editors let you do that between editing sessions. You can turn off your laptop, fly across the world, boot back up, and your undo history is still available because it’s serialized (saved) to a file somewhere on your hard drive.

The .NET Framework gives us a slew of serialization options, including System.Runtime.Serialization, System.Runtime.Serialization.Json, and System.Text.Json.Serialization. Each contains a set of classes designed to make serializing your objects to files fairly trivial. However, you’ll still have problems with private properties. Thankfully, Microsoft has supplied us with the DataContractSerializer class that helps you get around this limitation without resorting to an intricate class structure or worrying about the memento’s immutability.

State

I’ll confess looking back at the last eight chapters, I probably should have covered this pattern. You’ll use it, especially if you work in game development with Unity 3D. If you’re not familiar with this tool kit, it’s essentially a game engine capable of creating AAA games for consoles, PC, Mac, mobile, and the web. I taught game development with Unity for many years at a local college and so I have a soft spot for it, despite never having worked as a professional game developer. The Unity 3D game engine uses a finite state machine to control the animation of your game characters. It uses a visual editor to define the states and the animation to use when the game character’s state changes.

The State pattern entails changing an object’s behavior based on changes to its internal state. An object’s state is conceptually just the values of all its properties at a point in time. If you make a game where you run around the countryside fighting zombies, you might define your zombies with different behaviors. Most of the time, they’re just shambling around randomly looking for fresh BRAINS!

When something with a brain comes into view, the zombie’s behavior changes. It shambles towards the brain-toting organism while hissing “BRAINS!” over and over. Once the zombie is within arm’s reach, its behavior changes again. The zombie attacks!

We have one object with three behaviors, all controlled by the internal state. When we put the behaviors and states together, we form what’s called a finite-state machine. It is finite because zombies have a finite number of behaviors. In this case, the number is 3. The finite state machine can be diagrammed as shown here:

Figure 8.11: A finite state machine representing zombie behavior in a video game.

Figure 8.11: A finite state machine representing zombie behavior in a video game.

This state diagram shows the transitions between the different states. The zombie can’t randomly attack a victim that is too far away. It has to see the victim and close the distance first. A diagram of the pattern appears here:

Figure 8.12: The State pattern.

Figure 8.12: The State pattern.

Let’s break it down, as follows:

  1. The IState interface defines the behaviors that are possible. Our zombie can patrol, close distance to a visible victim, and attack.
  2. The Context class holds a reference to an object implementing the IState interface, which is used to communicate with concrete state objects.
  3. Concrete state objects contain state-specific methods.

The context object can be used to change the state by swapping concrete state objects held in the private state property. This means the context can enforce any logic for state transitions.

I didn’t include the State pattern earlier because it is very similar to the Strategy pattern covered in Chapter 5 . Kitty and Phoebe used the Strategy pattern to create a navigation system for bicycles. The behavior of the system changed depending on what type of terrain the user requested.

Template Method

The Template Method pattern is very similar to the Strategy pattern, which we covered in Chapter 5. This is a behavioral pattern that allows you to define the structure of an algorithm but defer the implementation to subclasses that override the actual logic but not the structure.

In Chapter 5 , Phoebe designed a navigational computer for bicycles. It used the Strategy pattern to compute navigational routes depending on whether the rider wanted a route via paved roads, unpaved gravel roads, or extreme terrain. We could have done the same thing with the Template Method pattern, which is why I didn’t feel the need to cover both.

The structure can be seen in the following diagram:

Figure 8.13: The Template pattern.

Figure 8.13: The Template pattern.

Let’s look at the numbered parts, as follows:

  1. The abstract template class defines the structure of a class that forms an algorithm. The steps to the algorithm are defined as abstract and can be overridden by a concrete implementation in a child class. The steps, however, are called in a method within the template class. In my example, I called it ExecuteTemplate(), which is not overridden. ExecuteTemplate() calls the steps in order, and this never changes. The template defines overrideable logic, but the structure is consistent each time the template is used.
  2. These are the concrete classes that override the algorithm steps. Note the AlgoImplementationA class overrides all the steps in the parent class. The AlgoImplementationB class just overrides a few.

This pattern allows you to make very flexible algorithm implementations using nothing more than common household inheritance.

Visitor

The Visitor pattern is another Behavioral pattern designed to help you “bolt on” new behaviors to existing objects. Its motivations are focused on SOLID principles (head back to Chapter 2 if you need an explanation of this acronym). The open-closed principle is honored because you are adding behavior without modifying existing classes. The SRP is honored because usually, the behavior you are adding is new and may have little to nothing to do with the purpose of the original class.

The idea behind the name comes from the idea that an object encapsulating new behavior can visit an existing, established class, allowing it to perform the new behavior. You’re literally teaching an old dog object new tricks.

Imagine a world where your body has an ability slot. You can slot in a new behavior as easily as slotting a memory card into a camera. Need to learn to fly a helicopter to escape evil secret agents? Slot the card with flight training and you can instantly fly any civilian or military aircraft! Do you need to cook like a 5-star chef? Slot a card with cooking skills and you’ll be able to defeat Gordon Ramsay or Bobby Flay in any cooking competition! Do you need moves at the nightclub? Slot a card with nightclub skills and you’ll dance the night away! Just be careful with that last one. There’s no telling what other skills might be on the card.

Let’s shake that last example off and look at the following diagram:

Figure 8.14: The Visitor pattern.

Figure 8.14: The Visitor pattern.

Let’s break it down, as follows:

  1. Our Visitor interface defines a set of methods required to implement a new superpower you wish to confer on an existing object graph. We’re fortunate that C# supports method overloading. Method overloading refers to a language’s ability to reuse method names as long as the method signature is different. If you’re not sure what this means, check out Appendix 1 at the end of this book. We cover it there. Not every OOP language supports this capability, but C# does. Our interface defines several methods with the same name but with different argument types.
  2. A second interface called the Element interface defines how visitors are accepted. If you want new abilities in our future world, you’ll have to get a slot added to your skull so that we have someplace to insert our ability cards. Likewise, your class will need an additional method based on this interface. You don’t need to change any existing methods or logic in the class. You just need to add a method to accept visitors.
  3. Concrete visitor classes implement our interface and provide the new behavioral logic. Note that we did not use the IElement interface as the argument type. The concrete object type passed in determines which method is run based on method overloading.
  4. These are your existing classes implementing the Element interface.

A few things come to mind when I think of the Visitor pattern in C#. First, it feels a lot like the Decorator pattern, but with a focus on object graphs instead of individual classes. Maybe that’s just me?

Another similar pattern is the Composite pattern covered in Chapter 5, and again in Chapters 7 and 8. Kitty and Phoebe used the Composite pattern to perform a set of calculations for weight and cost on a graph of objects used to comprise a bicycle drivetrain. Tom improved the design by weaving the Composite pattern into the object graph at design time. The difference is that the Composite pattern operates from outside the objects in the graph whereas the Visitor pattern is inserting new behavior.

Finally, the first time I read about the Visitor pattern, I immediately thought of extension methods. Extension methods are unique to C#. They allow you to add behaviors to existing classes using a separate class file. They are not an implementation of the Visitor pattern, but if your needs are simple, you might want to start with extension methods. If extension methods come up short, then try the more complex Visitor pattern.

Patterns beyond the realm of OOP

The field of OOP was really just the start. There are patterns beyond the realm of OOP that you have very likely heard of, but perhaps didn’t know were codified patterns.

Software architecture patterns

An obvious area for finding more patterns is within the domain of software architecture. It may seem like we’ve been talking about software architecture this whole time. We have. However, software architecture isn’t bound to OOP. Every pattern in this book relies on using C# because it is an OOP language. Software architecture patterns span every language and really help us define systems, not just enhance the structure of our code. Let’s look at some examples you have probably heard of before now.

Client-server pattern

We actually covered this one—we just didn’t call it out as a pattern. This pattern involves peer-to-peer (P2P) architecture consisting of a server, and usually many clients. The clients make requests to the server and do something with the result. This is pretty much how the internet works.

Microservices pattern

The Microservices pattern entails breaking large, monolithic applications into small, self-contained but interdependent services. The idea is to take the SRP to its ultimate implementation. Imagine a REST API with a handful of endpoints that together serve only one purpose such as password reset. Instead of password reset being built into a bigger API that does dozens of other things, the password reset becomes a microservice.

The idea is that it is easier to maintain tiny single-purpose APIs. The trade-off comes from the latency introduced between interdependent system calls and the resulting complexity of your network topology.

Model-View-Controller (MVC) pattern

Surely you’ve heard of this one! You see it in web applications when you split the application code into three layers, as follows:

  • A model layer that contains code that represents the data model, usually via an object-relational mapper (ORM).
  • A view layer that represents the user experience (UX).
  • A controller layer that represents the logic needed to accept requests from the view and process data returned from the model layer. This configuration embodies the ideals behind the separation of concerns (SoC).

Publish-subscribe (pub-sub) pattern

This is another popular pattern you’ll see in many guises. Sometimes you’ll see object-level implementations such as what we saw in the Observer pattern covered in Chapter 5 . You’ll also see higher-level architectural implementations in software such as Redis, RabbitMQ, and Apache Kafka.

In every case, the idea is to allow communication from a central source. Messages are sent to a publisher who in turn publishes the messages to relevant subscribers. This is vital to distributed architectures.

Command Query Responsibility Segregation (CQRS) pattern

This pattern is meant to solve the situation that arises when database queries for data occur far more frequently than database updates. If you have a reporting system where reports are generated but rarely subsequently modified, this is a good pattern to learn. Its solution generally entails segregating data that rarely changes but is commonly queried into a separate database called a data warehouse. This can boost performance on reads and writes because the responsibilities are segregated. The trade-off comes from the implementation cost, which often involves standing up a separate database server to handle the segregated loads.

Data access patterns

Data access patterns occur wherever you have traditional code interacting with a relational, or even non-relational, database. Since relational databases have been around since 1970 and are largely unchanged and completely ubiquitous, it shouldn’t be surprising that they have their own set of patterns. I cut my teeth with a book by Clifton Nock titled Data Access Patterns: Database Interactions in Object-Oriented Applications. I’ll list the book’s details in the Further reading section at the end of the chapter.

Here are just a few patterns from that book you’ll probably recognize.

ORM

Ever heard of Entity Framework (EF)? As a C# developer, you’d have to have lived beneath some sort of highly academic rock to have never heard of Microsoft’s flagship ORM. An ORM’s job is to map data between relational structures in Structured Query Language (SQL) databases such as SQL Server to objects in C#. If a pattern is a solution to a problem that occurs frequently, this might be the most important pattern in software development.

Implementations of this pattern, usually encapsulated in a third-party library, or in our case, within the .NET Framework, allow developers to work solely with C# objects. With an ORM, you never need to create or update table structures in your application’s database. You never need to figure out complicated joins or concatenate long SQL statements in your code. You create a set of objects that represent your database structure. You then query those model classes and work with data operations to manipulate your database.

The upside is if you don’t know—or don’t like—SQL, you never need to work with it directly. The trade-off is in application performance. EF code is known to run significantly slower at scale than direct connections to the database. It also adds one more layer of dependencies to your application. It’s just one more thing that can go wrong. If you’re sensing I’m not a fan, you’d be right. I like ORMs for small projects with a limited number of users. For example, if you’re making an app for internal use and you have limited time to create it, using an ORM might help you get the job done more quickly.

If on the other hand you are making an application for a large user base, eschewing the convenience of an ORM in favor of implementing our next pattern directly gives you tremendous control over performance by allowing you to leverage the full capabilities of your database software.

Active Domain Object

The Active Domain Object (ADO) pattern is a fun one because when you see ADO, you probably think of either Microsoft’s ActiveX Data Objects (ADO) or ADO’s older cousin Data Access Object (DAO). Then again, that might only be if you’re over 35 years old. It’s been a good while since those technologies were front and center.

The ADO pattern encapsulates data access and relevant object implementations. Their aim is to remove any direct interaction with the database outside the pattern implementation. Sound familiar? That’s pretty much what Microsoft ADO and DAO were designed to do. I doubt this is a coincidence.

Most developers don’t use this pattern, having generally moved to working with ORMs such as EF. However, if you have SQL skills, you can often make a more performant application by accessing the database directly via native drivers.

Demand cache

My wife and kids do this all the time. Just kidding, but not really. There are quite a few cache patterns. A demand cache is embodied by a cache that is populated lazily “on demand”. The main force at play here is you don’t know when the data will be needed, but you would like to only pay the price of retrieving the data once.

My application includes a demand cache as part of a web application. Some of the queries in my application return large recordsets or include considerable processing that can take several seconds to complete. That doesn’t sound bad, but for a web application, it is an eternity. My demand cache method checks the cache (which in my case is Redis) first when data is requested. If it’s there, the data is served from the cache. If it isn’t there, I retrieve and cache it. The first customer to request the data pays the performance penalty, but every customer after that gets the data very quickly.

Transaction

This is another ubiquitous data processing term that is really a pattern. In database lingo, a transaction refers to multiple SQL statements that must be completed as a unit. For example, if you have an automatic teller machine and you want to withdraw money from one account and add it to another, you need this to occur as one unit of work—a transaction.

You withdraw $100 from account A and add $100 to account B. If either SQL statement fails, you need to consider the whole transaction a failure and roll back all the changes entirely.

Optimistic and pessimistic lock

Nothing drives application developers crazier than dealing with locks in the database. An optimistic lock takes place in order to prevent missing database updates. You find this pattern used in inventory management systems (IMSs) where up-to-the-second, on-hand stock data is vital to selling that inventory. Last weekend, I bought a new clothes dryer at a popular store. My wife picked out the perfect dryer and the sign above the dryer said, “Order now and you’ll receive it in 3 days”. The salesman went to enter the order and found the units were not only out of stock, but the manufacturer wasn’t accepting back orders.

The sign assumed stock at the warehouse and was based on stale information. Now, replace the sign with a database query. The sales application queries to see if any dryers are in stock. At the exact same moment, at a store across town, someone else has just purchased the same model of dryer.

How can the clerk at the first store know whether there really is a dryer available? The optimistic lock pattern entails creating a version number on each row in a database table. The database in this scenario will optimistically assume the dryer is available if the row version number hasn’t changed since the transaction started and doesn’t lock the database row for update.

By contrast, in the same scenario, a pessimistic lock will prevent the clerk’s sale from going through if the second clerk’s transaction was in the middle of updating the inventory row data.

Creating your own patterns

Do you think you have an idea for your own design pattern? The GoF book presents a boilerplate documentation framework for publishing your own patterns. I won’t duplicate it fully here, but I will outline it for you. It involves four essential elements, as follows:

  1. Name and classification
  2. The problem description
  3. The solution description
  4. Consequences of using the pattern

Let’s talk a little about each section.

Name and classification

Every pattern needs a name that describes the pattern. Most of the patterns out there have names that make you think of general words from normal language. The word memento refers to a physical object that invokes memories of times past. The word singleton invokes the idea there’s a single thing. Come up with a short, memorable name that invokes the idea behind the pattern.

You also need a classification. In this book, we observed the three classifications in the GoF book: Creational patterns, Structural patterns, and Behavioral patterns. You saw in the previous sections that other knowledge domains that use patterns have their own lists of classifications. Maybe your pattern fits into an existing classification. If not, you’ll need to invent a new classification.

It is also possible your pattern might have an alias; an “also known as” (AKA) name. The Decorator pattern is also known as a wrapper. Document any aliases for your pattern if any exist.

The problem description

This section of your documentation describes the problem your pattern aims to solve. When the GoF describe the problem, they break it into several smaller sections, as outlined here:

  • Intent refers to the overall goal of your pattern.
  • Motivation/Forces outlines why someone would use your pattern. This is deeper than “because it’s cool” or because it solves a particular problem. When we discussed antipatterns in the first chapter, we described a set of forces that caused the antipattern to come into being. In this section, we’re looking for similar driving factors around why your new pattern is relevant and useful.
  • Applicability lists the context for the pattern. Make a short list of situations where the pattern is useful.

While it isn’t necessary to fill in every single niche, you’ll recognize elements we’ve used in this book.

The solution description

When describing the solution, you need to describe the elements used to construct the design. Expound on the relationships between the elements. Describe how the elements collaborate, and make sure to explain the responsibilities of each element. You might consider breaking this section into smaller chunks as well, as follows:

  • Participants list classes, objects, interfaces, enumerations, and so on that participate in the pattern solution.
  • Collaboration describes how the participants collaborate.
  • Structure will contain UML diagrams of a generic form of the pattern, and perhaps a diagram of a more real-life concrete use case. This is the format I have used in every pattern I covered in this book. I find the generic diagrams less useful by themselves. If a concrete example follows, developers can look at both diagrams and more easily relate them to their work.
  • Implementation contains a description of how to implement the pattern. In this book, I’ve used numbered diagrams to describe how the pieces fit together.
  • Sample Code or Pseudocode mostly speaks for itself. I would recommend real-world examples instead of class A inheriting from class B, which uses class C in composition. That’s too abstract. If you want people to use your pattern, find ways to make it relevant to their work.

Consequences of using the pattern

You should always discuss the positive outcomes and trade-offs that come with using your pattern. Most patterns have defenders and detractors. If you read up on patterns on the internet, you’ll see a healthy debate about how some patterns might be antipatterns. We presented such a case when we discussed the Singleton pattern in Chapter 3. The more complex a pattern, the more likely it is to have trade-offs. The Template Method pattern is extremely simple. I doubt whoever came up with that one lost any sleep over thinking about possible negative outcomes. In contrast, the Façade pattern presented in Chapter 4 presents a trade-off between the complexity of the third-party framework and ease of use for developers that don’t need everything the third-party framework exposes.

Not everybody likes patterns

We demonstrated in Chapter 3’s coverage of the Singleton pattern that not everybody agrees that patterns are a positive contribution to the field of software development. The usual argument is design patterns are simply workarounds for incapable, inefficient, or incomplete OOP languages. Academic literature has shown as many as 17 of the 23 patterns within the GoF book become unnecessary when you use languages such as List Processing (LISP) or Dylan. What? Who even uses those?

Another group of academic detractors advocates switching your paradigm from OOP to aspect-oriented programming (AOP) as a solution to all your problems. As you’re hopefully aware, OOP aims to solve problems by modeling things in the real world. AOP aims to model behavior as crosscutting concerns. AOP is not supposed to be a competitor with OOP, yet some arguments place it that way.

The bottom line is there will always be a debate that says “If you would only switch to language X, framework Y, or paradigm Z, all your pattern problems will be solved!” My retort to that is to remind you to beware of the Golden Hammer!

Summary

Patterns are everywhere. There is a field called biomimicry that aims to study technology inspired by patterns in nature. It’s difficult to talk about software development anymore without bringing up artificial intelligence (AI), whose main job is to find patterns in massive amounts of data using techniques called machine learning (ML). The software industry has been humming along now since 1843 when Ada Lovelace wrote what most consider to be the first computer program. In that time, we have collectively run into the same challenges and frustrations over and over again. Eventually, we got smart enough to start writing things down and talking about them.

We’ve been learning patterns, which at their core are really just a way of communicating our best ideas as they relate to organizing and optimizing our code.

In this chapter, we briefly covered the patterns from the original GoF book that we didn’t cover in the earlier chapters as part of this book’s story. I left these patterns out for one or two reasons in each case, as follows:

  • If a pattern was remarkably similar to another pattern already covered, I didn’t cover it. The State pattern is very similar to the Strategy pattern. The Strategy pattern fit my story, so that’s the one I used.
  • If a pattern was very complicated and rarely used, I didn’t cover it. The Memento pattern is a good example. There are easier ways to handle the use case of representing snapshots of objects such as .NET’s serialization features that negate the need for a complicated Memento pattern implementation.

We went beyond GoF patterns, and even beyond OOP patterns to list some common and highly familiar patterns in other domains of software and database architecture. We concluded with a synopsis of how you might go about documenting your own patterns should you ever discover a new one. I left you in this chapter with a final warning. You’re going to be tempted to close this book and run through the office like a crazy person yelling “PATTERNS!” everywhere you go. Don’t laugh. I’ve seen it happen. If you do this, expect some eye rolls. Not everybody thinks patterns are a good idea, and sometimes they are right. Patterns themselves can easily succumb to the Golden Hammer antipattern. Remember—patterns are here to simplify and improve your software. If they make things slower or more complicated, you must abandon them in those cases. Patterns are tools, not dogma.

There’s just one loose end to tie up.

Sundance Square – Fort Worth, Texas

It was a hot spring day in Fort Worth, Texas. And it was the last day of the MS-150 bicycle rally. The MS-150 is an event that generates millions of dollars per year toward research into a cure for multiple sclerosis (MS). Thousands of cyclists in the Dallas area ride in the 150-mile, 2-day event. Most of the local bike shops have tents set up in Sundance Square, a shopping and entertainment district in the heart of Fort Worth. Bumble Bikes, being a platinum sponsor, has a large tent set up at the finish line.

Tom, Lexi, and Karina are working the tent, giving water and high-fives to the intrepid few that cross the finish line. While upward of 3,000 riders start the rally, fewer than 10% actually finish. Most quit along the way when their knees give out or their equipment breaks. Tom and Lexi are watching keenly for a group of riders who they fully expect to finish dead last. It’s a rally, not a race. The people who ride aren’t competing against each other—they’re competing with themselves to see if they have what it takes to complete the course. They start in Plano on day 1 and pedal 75 miles to Texas Motor Speedway where local National Association for Stock Car Auto Racing (NASCAR) races are held. The next day they start again but first, they do a lap on the speedway, then ride 75 more miles into Fort Worth.

There they are!”Lexi yells as she squints toward the top of a hill in the distance. Team Bumbles is cresting the hill, all riding brand new Hillcrest bicycles: Kitty, Phoebe, and most of the Bumble Bikes employees, along with Kitty and Phoebe’s father.

10 years have passed since his diagnosis. The chemotherapy and steroids had been ineffective in treating the disease. In fact, the steroids had caused a severe case of osteoporosis. The doctors had given up, saying there was nothing further that could be done. Kitty and Phoebe had prayed countless times, ultimately joined by an informal coalition of local churches.

The wheelchairs Kitty and Phoebe built were extremely helpful. They had built and distributed over 1,000 wheelchairs free of charge to children’s hospitals all over the world. Bicycle and wheelchair sales at Bumble Bikes grew steadily until it became the number 3 bicycle manufacturer in the world, and the only company to produce its products in the United States (US). MegaBikeCorp, the company where Kitty and Phoebe interned all those years ago, was ranked at number 19.

Their father had been using his Texas Tank for many years. The design had proved too expensive to mass produce, so they only made one. As much as he loved his Tank, every day he made it a practice to try to stand up. Most days he fell down. Until one day, he didn’t.

As suddenly as it had started, one day it stopped. The doctors could not explain it. They had run out of ideas years ago. Kitty and Phoebe knew their prayers had been answered.

It took years of physical therapy. Electrical shock therapy was needed to get their father’s voice box working again after the disease had nearly destroyed the muscles in his throat. He took shots in the stomach every day for 2 years to cure the osteoporosis. He took short walks—a few blocks at first, but later it was a few miles. Month after month, and year after year, he got a little stronger. He traded his Texas Tank for one of Bumble Bikes’ new electric bicycles with a motor. Tom was happy to take the Tank off his hands. The new electric bike offered a motor to assist with pedaling. The motor didn’t do all the work—it just helped.

Eventually, he was able to ride a normal bicycle again, and today, he had ridden it 75 miles to the finish line with his daughters and a small armada of handicapped riders. Bumble Bikes had produced a hand-cranked bicycle for riders who couldn’t use their legs and invited any rider who didn’t have such a bicycle to ride with Team Bumbles.

Everyone in the square stopped what they were doing and cheered as they saw the back of the pack, followed by a police escort crossing the line. It had been a very long road, not just because 75 miles is a long way to ride a bicycle in 1 day. The finish line meant so much more than the end of the rally. Most importantly, they crossed it as a family.

Further reading

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset