Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Real-World Implementation of C# Design Patterns
Real-World Implementation of C# Design Patterns

Real-World Implementation of C# Design Patterns: Overcome daily programming challenges using elements of reusable object-oriented software

eBook
€19.99 €28.99
Paperback
€35.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Real-World Implementation of C# Design Patterns

There’s a Big Ball of Mud on Your Plate of Spaghetti

Welcome to what is potentially your last day on the job. Your project is about to be canceled. Your customers are angry. Your boss is freaking out. Your boss’s boss is in Antigua, but when she comes back next week, heads will probably roll. There’s no way to sugar-coat it. You might want to update your résumé and brush up on your algorithms, so that you’re ready for an imminent job search.

How did it come to this? We had a plan. The hardware architecture was simple. The first few releases went off without a hitch and our users were delighted. Our client even presented a whole new set of feature requests and signed a contract extension. How have we found ourselves at the precipice of sure and sudden doom?

The situation we have found ourselves in here is far from unique. According to many academic accounts, five out of six software projects are canceled. Others fail by running behind schedule or over budget. Software projects are hard. There’s no such thing as a simple program. There’s really no project that you can knock out in a week, then ship, and that’s the end. It doesn’t work that way. This phenomenon is unique to the software industry. Structural engineers who design bridge are pretty much done when the bridge opens for public traffic. Electrical engineers design and test circuits on breadboards, then hand those designs off to be manufactured. Aeronautical engineers, such as my grandfather, who designed power plants (as in, engines) for Beechcraft, generally designed and prototyped engines, but didn’t do much beyond that. It was up to others to manufacture the engine, mount it on the aircraft, and others to maintain the engine.

In contrast, software engineers must design, build, test, and often maintain the systems that they develop in a continuous delivery environment. Many projects are never “done.” I’ve been working on the same software project for the last 9 years. We certainly haven’t built the perfect project with perfect architecture, but the project has endured. New features are produced, and bugs are discovered and fixed.

What differentiates projects that run continually for many years from the vast majority that get canceled? While there are many ways this can happen, we’re going to focus solely on the design and architecture of our software. I’ll start with how it very often goes wrong. In keeping with the title of this book, we’ll spend some time in this chapter discussing a set of antipatterns. While I haven’t directly introduced the concept of patterns yet, I suspect that you can make an educated guess about what they are, along with what their antithesis is. A pattern, for now, is simply a formally explained, abstract, best practice solution to a common development requirement. An antipattern is a formal example of what you shouldn’t do. Patterns are arguably good. Antipatterns are inarguably bad.

This chapter will present some of the most common antipatterns, including the following:

  • Stovepipe systems
  • The Big Ball of Mud
  • The Golden Hammer

Once we’ve learned about a few antipatterns, we’ll focus in later chapters on principles and patterns designed to combat and correct the circumstances where antipatterns have either taken hold or might soon take hold. In Chapter 2, Prepping for Practical Real-World Applications of Patterns in C#, I will prepare you for your work with patterns. Software development is an odd business in that we all come to it traveling different roads. I am personally self-taught. I started when I was 12 years old. The only books about computer programming were available for purchase at Radio Shack. There were about a dozen. We didn’t have resources such as Packt Publishing plying the market with fascinating and useful books on every facet of software development. In 1991, the year that I graduated from university, a computer science degree would have focused on software development for mainframes using FORTRAN, which is a far cry from the work I do now. The mainframe programming course I took in 1987 was the last class to use punch cards. If you’re not sure what they are, go look them up. I’ll wait. Are you back? Are you horrified? Me too. The point is, there are a lot of people such as me out there who learned programming out of necessity, and they learned informally.  

There are many university-trained software developers out there, but not all software development programs are the same. Computer science programs focus on elements such as mathematical theory and algorithm development but teach only a minimal amount of practice. Software engineering programs, boot camps, and trade schools have more of an engineering focus, where you learn to build software with less of a focus on the theory. Regardless of where you started, Chapter 2 aims to ensure that you understand the most important formal engineering concepts needed in order to work with patterns. Patterns were created using a set of rules and Chapter 2 covers those rules.

In Chapters 3, 4, and 5, we cover patterns in earnest using a story format. I’ve done this in the hope of creating a learning experience very different than my own, having read some of the more heavy-handed academic treatments of design patterns. The patterns that I have selected for this book come from what is perhaps the seminal work on patterns in the software industry, Design Patterns: Elements of Reusable Object-Oriented Software, by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides. These four authors are known collectively as The Gang of Four (GoF), and the book that they wrote is colloquially referred to as The GoF book or just GoF. The GoF book contains 23 patterns, broken down into three categories:

  • Creational patterns deal with the creation of objects beyond simply using the new keyword.
  • Structural patterns deal with the way that you structure your classes to maximize flexibility, reduce tight coupling, and help you focus on reusability.
  • Behavioral patterns deal with how objects interact with one another.

The creational patterns I’ll be covering in Chapter 3 include the following:

  • The Simple Factory (technically not a pattern)
  • The Factory Method pattern
  • The Abstract Factory pattern
  • The Builder pattern
  • The Object Pool pattern
  • The Singleton pattern

Within the realm of structural patterns, in Chapter 4, I’ll be covering the following:

  • The Decorator pattern
  • The Façade pattern
  • The Composite pattern
  • The Bridge pattern

Practical pattern coverage will conclude in Chapter 5 with this set of behavioral patterns:

  • The Command pattern
  • The Iterator pattern
  • The Observer pattern
  • The Strategy pattern

Again, I’ll point out that this book is designed to focus on real-world software development. In the real world, we don’t always perfectly follow the rules presented in Chapter 2. Many books present a perfect experience. Expert authors always present all of their examples as perfect on the first try. I won’t be doing that because that isn’t in keeping with reality. As we flow through Chapters 3, 4, and 5, we will find ourselves faced with “gotchas” that you will find in actual practice. There’s no way around them. Even if you execute the patterns and strategies in these chapters perfectly, nobody can foretell the future. In Chapter 6, a plot twist in our story arises and we have a chance to rethink everything that we’ve done up to that point. In Chapter 6, we’ll design a new system based on an old one using the patterns that we’ve learned so far. Chapter 6 is all about creating a design and a plan. In Chapter 7, we implement that plan.  

I won’t be covering every pattern covered by the GoF. Instead, I’ll be focusing on the patterns you are most likely to need as a C# .NET software developer working in the field. I’ve selected my list of patterns based on popularity, usefulness, and complexity. Complex patterns that are not often seen in the wild have been omitted from the main text of the book. That said, I do circle back in Chapter 8 to give you a rundown on the patterns that I didn’t cover, and the usual advice on where to go from there.

This book assumes that you have a few years of experience working with C#. In addition to my day job, I have taught software development at colleges for the last 25 years. I presently teach at Southern Methodist University’s Full Stack Code Bootcamp. Some of the programs I taught focused on C#, while others haven’t. At SMU we teach JavaScript. If you’re coming to this book without recent C# experience, or perhaps with none at all, I’ve added Appendix 1 at the end of the book. It is designed to give you what I hope is enough orientation in the C# language to make the rest of the book useful. The truth about patterns is they are language-agnostic. They apply to any object-oriented language, and I’ve even seen some of them shoehorned into languages that are not object-oriented with arguable levels of success.

Technical requirements

This chapter presents some code examples. Most books always present exemplary code that makes sense for you to follow in the creation of the project. The code in this chapter is terrible on purpose. It isn’t strictly necessary that you follow along by creating the project, but you’re welcome to do so if you’d like.

If this is the case, you’ll need the following:

  • A computer running the Windows OS. I’m using Windows 10. Since the projects are simple command-line projects, I’m pretty sure everything here would also work on a Mac or Linux, but I haven’t tested the projects on these operating systems.
  • A supported IDE, such as Visual Studio, JetBrains Rider, or Visual Studio Code with C# extensions. I’m using Rider 2021.3.3.
  • Some version of the .NET SDK. Again, the projects are simple enough that our code shouldn’t be reliant on any particular version. I happen to use the .NET Core 6 SDK and my code’s syntax may reflect that.
  • An instance of SQL Server and basic knowledge of SQL. I want to restate that the code in this chapter is designed to be a realistic example of throwaway code. C# and SQL Server go together as peanut butter and jam do, which adds to the realism. Some readers may not be comfortable working in SQL Server, especially without Entity Framework (EF) used for its presentation. This is the only place in this book where a database is even mentioned. If you have no experience with databases, don’t worry. The example is really meant to be read more than tried. If you want to try it, any version of SQL Server should work. I’ll be using SQL Server 2019.

You can find the code files for this chapter on GitHub at https://github.com/Kpackt/Real-World-Implementation-of-C-Design-Patterns/tree/main/chapter-1/.

No battle plan survives first contact with the enemy

There’s an old saying: if you fail to plan, you plan to fail. Only the rankest amateur would dive into a project IDE-first without at least considering how the project ought to be structured. The typical first steps might involve roughing out a package and object structure, or maybe designing the structure of a relational database that will persist the data used by our software. Someone who’s got a few projects under their belt might even draw some diagrams using the Unified Modeling Language (UML).

We begin by taking a set of user stories and we shape our code into something that on the surface meets the requirements in front of us. Soon, we’re in an agile groove swing. We’ve achieved velocity! We create a feature, show it to the customer, get feedback, revise, and continuously deliver. That’s usually how the troubles begin.

Our first major anti-pattern, the stovepipe system, comes from the seminal book on the subject, AntiPatterns, by Brown, et al., which I’ve listed as suggested reading at the end of this chapter.

The Stovepipe system

Once upon a time, in just about any industrialized society, people heated their homes and cooked using a cast-iron potbelly stove. These stoves burned coal or wood for fuel. Over time, the exhaust vent for the stove, called the stovepipe, since it was literally a pipe sticking out of the stove, would build up with corrosive deposits, which led to leaky stovepipes. The fumes from a burning stove are potentially life-threatening within a small, enclosed space.

Here’s what an actual stovepipe looks like:

Figure 1.1 – A stove with a stovepipe.


Figure 1.1 – A stove with a stovepipe.

The stovepipe required constant maintenance to prevent asphyxiation. This was usually done by the owner of the stove, who was unlikely to be a stove repair professional. The stove was repaired using tools and materials that were readily available. This made for very ad hoc patch jobs, rather than clean, well-thought-out repairs done with original equipment manufacturer (OEM) grade materials and the proper tools.

Now, think about how this might relate to a software project. The initial release is designed with great care, with an implementation that perfectly matches the design. The natural tendency during software maintenance is to fix things quickly and get the patched version released and out the door. As with our amateurish stove repairs, our analysis of the holes in the software design and implementation are perfunctory and incomplete. There is pressure to solve this quickly. Everybody is watching you. Every minute the application is down costs the company money and you risk losing the employee of the month parking spot.

This happens to everyone and everyone generally caves to human frailties. You then implement the quickest, easiest thing you can think of – it’s done and the patch is out the door. The crisis is over.

Or is it? Dun dun dun! Small ad hoc fixes have a negative cumulative effect over time, referred to as technical debt, just as the corrosive deposits on a stovepipe do. How can you tell whether the systems that you’re working on are stovepipe systems? Let’s explore the following:

  • Stovepipe systems are monolithic by their very nature. It is not easy to get data in or out of this kind of system, and integrating software built this way into a larger enterprise architecture is cumbersome or impossible.
  • Stovepipe systems are very brittle. When you make one of these small ad hoc repairs, you generally find that the fix breaks other parts of the application.  Usually, this isn’t discovered until after the breaking fix has been released.
  • Stovepipe systems can’t be easily extended as new business requirements emerge. When a project starts, you’re given a set of requirements. You build the software that meets those requirements. After it’s released, a new feature is requested that you couldn’t possibly have predicted. You realize that there’s no way to implement that feature without redesigning the whole app. Anytime you’re tempted to throw out the baby with the bathwater and just start over, you’re working on a stovepipe system.
  • Stovepipe systems built on component architectures are generally incapable of sharing those components with other enterprise applications. The level of code reuse between projects is very low.
  • Stovepipe systems are often found on projects with high turnover. This makes sense. You start a new job, replacing the last developer, and you feel pressure to get something working quickly to show your new boss that hiring you wasn’t a huge mistake. You do your best to piece something together to fix a problem. You have no knowledge of the existing architecture or what’s been tried, and perhaps failed, in the past. Now amplify this by considering two or three further re-staffing efforts, with several months between each new hire’s start dates.
  • Stovepipe systems are often indicated when the development team is using new or unfamiliar technologies, stacks, or languages. Given that the same pressure to produce something quickly exists, while the team is simultaneously required to work with tools and languages that they’ve never used before, this leads to the same pattern of just getting something working and released. You will also encounter stovepipe systems in start-ups, corporate acquisitions, and mergers for these same reasons.

Does any of this sound familiar? Naturally, we’re not talking about anything you’ve ever written! Isn’t this just a little bit reminiscent of code you’ve seen other people write? Maybe your competitors? Maybe your students? Even if you own up to writing a stovepipe system, don’t beat yourself up. It is far and away the most popular pattern in software development today. Sometimes, a stovepipe system is fine. Remember, not every physical edifice needs to be supported by fluted ivory columns, and there’s a very legitimate argument to be made for getting the software to market and worrying about the rest later. However, if your objective is to build software that’s still useful and profitable 10 years or more down the road, keep reading. We’ll have those stovepipes replaced with functional, modular, well-constructed systems in no time.

The Big Ball of Mud

Around the same time Brown, et al. were writing their book on antipatterns, another research team was engaged in a similar effort. The product of their work was titled Big Ball of Mud, by Foote and Yoder (1997), which I’ve listed in the Further reading section at the end of this chapter. It’s their work that inspires the title of this chapter.

The Big Ball of Mud antipattern is remarkably similar to our outline of stovepipe systems. However, the authors go into greater depth on how these systems understandably come to be.

They often start with throwaway code. I think this is self-explanatory. It’s code that you knock out in a few hours, or even a few weeks, that serves as a rough prototype. It proves to you and perhaps your stakeholders that the problem in front of you is soluble. It might even be good enough to demonstrate to the client. This is where the trap is sprung. The prototype is good enough to publish, so, at the behest of your boss, you do. We’ll simulate this later in this chapter in a section titled A throwaway code example by intentionally building a prototype that is good enough to ship, but not good enough to survive extension. This rough prototype will do everything asked of us. Right after this imaginary software project ships its first release, we’ll then find ourselves faced with a second factor in the construction of a Big Ball of Mud: piecemeal growth. A project manager might refer to this pejoratively as scope creep. I have been a consulting software engineer and a corporate software engineer. I can tell you the project management view of scope creep as being something negative is misinformed. While it is a source of frustration from a planning and billing perspective, new requirements coming in after the initial release are the hallmark of a successful system. It is my strongest advice that you begin every project with the idea that it will be wildly successful. This may seem overly optimistic, but it is in fact the worst-case scenario if you have published throwaway code.

Piecemeal growth leads to a strategy called keeping it working. Again, this needs little explanation. As bugs and new features are identified, you just fix the offending bits and make the program satisfy the new set of requirements. And, oh, by the way, we need this done by next week.

After the second release, keeping it working becomes your daily job description. If your program is really successful, you will start to hire people to help you with keeping it working, which naturally amplifies the problems and technical debt as the project continues to grow.

To reiterate, this sounds very similar to our elucidation of a stovepipe system. Foote and Yoder consider in more detail the forces that lead to our unfortunate muddy circumstances. Forces, as in nature, are outside actors that you can rarely ever control. These forces consist of the following:

  • Time
  • Cost
  • Experience
  • Skill
  • Visibility
  • Complexity
  • Change
  • Scale

Let’s talk a little more about each.

Time

You may not be given enough time to seriously consider the long-term ramifications of the architectural choices that you’re currently making. Time also limits your project by limiting what you can accomplish with what you’re allotted. Most developers and project managers try to get around this by padding their estimates. In my experience, Parkinson’s law is true: projects where time estimates are padded, or even doubled, usually expand to fill or exceed the time allotted.

Cost

Most projects don’t have an infinite budget. Those that do are open source projects that have no monetary budget at all, instead substituting it with the time of volunteers, which is itself a cost. Architecture is expensive. The people with the knowledge and experience to develop a sound architecture are rare, though slightly less inaccessible given that you are reading this book. They tend to draw higher salaries, and the expense involved in the effort to create and maintain proper architecture doesn’t pay off immediately in the minds of your stakeholders, bosses, or customers.

Good architecture requires time, both on the part of the development staff and the architect, but also domain experts who know the business behind the software. The domain experts are rarely dedicated to the software development effort. They have regular jobs with real requirements and deadlines outside the software project. Involving a business consultant who bills at $250 USD per hour is eating up time that could be billable, but you honestly can’t complete the project without this access.

Experience

Software developers are experts in software development. They rarely have expertise in the business domain where they are building solutions. For example, it’s rare that someone building a system that quotes insurance policies has worked as an actuary or even an adjuster. Lack of experience in the business domain makes the job of modeling software a process of trial and error, which naturally affects the program’s architecture.

Skill

Not all software developers have equal levels of skill. Some are new to the field. Some are slower learners than others. Some learned to use a few golden hammers (more on this later) and refuse to upskill any further. And there’s always a superstar on the project that makes everybody else feel as though they’re a poser.

Visibility

You can’t see inside a working program. Sure, you can fire up a debugger, but normal people can’t look around the architecture of your code the same way that they can inspect the architecture of a physical structure such as an office building. For this reason, architecture is neglected. Your boss will not likely give you a fat bonus for your amazing abstractions and interface structures. They will, however, reward you for shipping early. This leads to a very human, lackadaisical attitude toward how your code is structured.

Complexity

Complex problem domains beget muddy architectures. Imagine modeling a collection of modern light bulbs. That’s pretty easy. Properties such as wattage, light output in lumens, and input voltage jump out at you as if they are second nature. Now, imagine modeling a light bulb in 1878. You’re in uncharted territory. Thomas Edison patented his first light bulb in 1879 and is famously quoted as saying that he had discovered two thousand ways to not build a light bulb. If the domain is complex or unexplored, you should expect a bumpy ride as far as your architecture is concerned.

Change

Change is the one thing that always remains constant. Foote and Yoder wrote that when we devise an architecture, it is based entirely upon a supposition: a set of assumptions about the future wherein we expect changes and extensions to that architecture to be bound only to the realm of possibilities that we have considered so far. This is all well and good, except another truism invariably surfaces: no battle plan survives first contact with the enemy. The change requests will come in the most inconvenient form, at the most inconvenient time, and it’s your job to deal with that. The easy way out is always the most palatable to the stakeholders but it is what leads to a Big Ball of Mud.

Scale

Creating a system to be used by 100 people in total is a very different problem than creating a system that can process 10,000 requests per second. The style in which you write your code is different. Your reliance on highly performant algorithms is largely absent in a small system, but vital to the success of a large one. Rarely do projects start at the scale typically considered by Google or Amazon. Successful projects must be able to scale up according to how successful they become.

The Golden Hammer

Another important antipattern you should learn to recognize is generally a product of some marketing organization or salesperson in a company outside your own. It happens when some killer app, framework, infrastructure component, or tool is presented as the panacea for all your software development woes. It slices, it dices, it makes julienne fries, and it automatically refactors itself while speeding up the execution of your code.

The antipattern is described as the Golden Hammer. Behold a fully-rendered CGI representation in Figure 1.2:

Figure 1.2 – When you’re given a golden hammer, everything is a nail.

Figure 1.2 – When you’re given a golden hammer, everything is a nail.

Silicon snake oil salespeople will visit you, take you out to someplace fancy, and try to convince you that the database tool, platform, or whatever-as-a-service (WaaS) that they’re selling can be the entire basis for your company’s software. Consider Microsoft SQL Server for a minute. At its most basic, SQL Server is a relational database. It stores your data in tables that you can query. Related tables of data can be joined and filtered allowing a developer who understands the Structured Query Language (SQL) to produce reporting data in any format or configuration. This is a common functionality found in every relational database tool from Microsoft Access and SQLite to Oracle and Microsoft SQL Server. Since SQL is a standardized language, offering this basic functionality is little more than table stakes. Just so we understand each other, all puns are intended.

So, how could Microsoft expect to charge money for something that you can get for free in open source offerings such as MySQL and PostgreSQL? Granted, SQL Server got started before they did when there were fewer rivals in the marketplace, but SQL Server is one of the most popular platforms for managing data today. This is because SQL Server’s value contribution doesn’t end with tabular data storage. As the product has grown over the years, new features and ancillary tools have been added. You have the ability to load and analyze data in novel and sophisticated ways using SQL Server Analysis Services. SQL Server Reporting Services allows you to create reports using SQL and then present those reports graphically to whoever might need them by emailing the reports as PDFs. It also allows users to access the report on the server and play around with the data without needing to know SQL or have access to the underlying code.

There are supported workflows for working with AI and machine learning projects using R and Python, and you can make bits of code in C# that process in the database such as a native stored procedure. SQL Server Integration Services allows you to ingest and publish data to a variety of different databases, software services, and industry formats. This leads to the ability to integrate your software and services with your business partners and customers.

In short, if you tried hard enough, you could probably write a great deal of an application, if not an entire one, solely using SQL Server’s ecosystem. SQL Server is the Golden Hammer. Every problem now looks as if it’s something that can be solved with SQL Server. I want to point out that I am not vilifying SQL Server. It’s a reliable and cost-effective set of tools. I go out of my way to recommend it at parties and my advice is always well received. Note to self: find better parties. I picked on SQL Server because I’ve seen it happen with this particular tool. If you spend too much time reading the marketing material for SQL Server, it would be easy for you to walk away with the same conclusion: that SQL Server is all you need. Maybe it is, but you should only make that decision after you understand the Golden Hammer antipattern, lest you wind up painting yourself into a technological corner.

The Golden Hammer also emerges when a developer learns about some technology that was unknown to them before. They use it. They like it. They’re rewarded for it in the form of fast or novel solutions to a problem. Since that worked out so well, and since they’ve gone to the effort of adding a new skill to their skill set, they try to use that tool or technique to solve every problem that they encounter.

Once, I took over a project that was in trouble. The lead programmer on a small team was being let go and most of his team left shortly after I replaced him. Interpersonal drama aside, I set out to understand the new project and the business domain by going through the existing code base.

I asked around and, as it turned out, the original staff on the project were with a consulting firm. The firm sent a couple of their ace developers over to meet and gather requirements. Upon seeing the prototypes that the client had themselves produced in Excel, using Visual Basic for Applications (VBA), the consultants concluded that they could produce real code in a real language and have a fully converted program running in under a month.

Two years went by with no usable deliverables. The ace developers either grossly underestimated the prototype they were working with or overestimated their capabilities. I think it was a little of both. Most developers look down their noses at VBA. I’ll admit that I used to, even though I’ve written quite a bit of VBA code. The consultants erroneously concluded that VBA is simplistic. They believed anything written in VBA would involve a trivial amount of effort to convert to a language as powerful as C#, backed by the equally powerful .NET Framework and SQL Server.

After a few months with very little progress, the consulting firm pulled their ace developers off the project to work on something else and the project was staffed entirely by junior developers.

Given the antipatterns we’ve covered so far, you can already see where this story is headed. I inherited this code after two and half years without a viable release. As I went through the existing code, I was able to see where the junior developers had encountered some tool or technique. It was as if I was looking at rings on a tree:

Figure 1.3 – The effects of new developers discovering a golden hammer are analogous to tree rings in their code.

Figure 1.3 – The effects of new developers discovering a golden hammer are analogous to tree rings in their code.

You can tell exactly where they have learned that a stored procedure can be used in SQL Server because, from that point forward, the business logic suddenly moved out of the code and into the database. This is usually a bad idea. It’s often done because you can change the business rules without compiling and publishing a new executable, allowing you to make minor or major adjustments. This is roughly akin to working on the engine of an airplane while it’s flying at 1,261 knots (about 1,453 mph or 2,336 km/h) at 30,000 feet (9,144 m).

Somewhere else, you can tell they have read a book on patterns because the code changes. Suddenly, everything has interfaces and uses the factory pattern, which we’ll cover later in Chapter 3. Some of this was good. I could see that they were improving. However, a lot of it was someone picking up a new hammer and using it to bang everything around it into the shape of something useful. This was evident mainly because they were never given a chance to go back and refactor their earlier work. They used different techniques at different points in time. They weren’t always using the best tool for the job, but they were motivated by the forces we discussed earlier. They did their best with what they had, as with our stovepipe repair jobs.

A throwaway code example

Let’s take a look at some throwaway code. Remember, throwaway code is written quickly, with little thought to architecture. It’s good enough to ship but not good enough to survive extension. Consider a program designed to ingest log data from a popular web server, and subsequently analyze and present salient information in the form of a report rendered in HTML. You will be analyzing logs from NGINX (pronounced ‘engine-ex’), one of the most popular web server programs in use today. I usually write a user story in an issue tracker, but I’ll write it as a Markdown file in lieu, and I’ll include it with my project so that I have a record of my requirements:

As an IT administrator, I would like to be able to easily review weblog traffic by running a command that takes in the location on my computer of a log file from a server running NGINX. I would also like to store the data in a relational database table for future analysis.
GIVEN: I have a log file from NGINX on my computer at c:\temp\nginx-sample.log AND
GIVEN: I have opened a PowerShell terminal window in Windows 10 or later AND
GIVEN: The WebLogReporter program is listed within my computer's PATH environment variable.
THEN: I can run the WebLogReporter command, pass the location of the weblog and the path for the output HTML file.
GIVEN: The program runs without errors.
THEN: I am able to view the output HTML file in my favorite browser.
Acceptance Criteria:
* It's done when I can run the WebLogReporter program with no arguments and receive instructions.
* It's done when I can run the WebLogReporter program with two arguments, consisting of the first being a full path to the NGINX log file I wish to analyze and the second being the full path to the output HTML file I would like the program to produce, and I am able to view the output HTML file within my browser.
* It's done when all the log data are stored in a relational database table so I can query and analyze the data later.

Your team decides to use C# and SQL Server to read, parse, and store the data for analysis. They decide that, while there are several good templating systems out there, nobody on the team has ever used any of them. Time is short and HTML is simple, so we’ll just write our own code to convert our results represented by the results of SQL statements. Let’s dive in! The requirements stipulate a console application, so that’s the project type I used when creating it in my IDE. I won’t be walking you through creating the project. I’m assuming you know how to create a console application using the new project options in Visual Studio.

The input data from an NGINX log looks as follows:

127.0.0.1 - - [16/Jan/2022:04:09:51 +0000] "GET /api/get_pricing_info/B641F364-DB29-4241-A45B-7AF6146BC HTTP/1.1" 200 5442 "-" "python-requests/2.25.0"
127.0.0.1 - - [16/Jan/2022:04:09:52 +0000] "GET /api/get_inventory/B641F364-DB29-4241-A45B-7AF6146BC HTTP/1.1" 200 3007 "-" "python-requests/2.25.0"
127.0.0.1 - - [16/Jan/2022:04:09:52 +0000] "GET /api/get_product_details/B641F364-DB29-4241-A45B-7AF6146BC HTTP/1.1" 200 3572 "-" "python-requests/2.25.0"

When you create a console app project in Visual Studio, it creates a file called Program.cs. We’re not going to do anything with Program.cs yet. I’m going to start by creating a new class file to represent a log entry. I’ll call it NginxLogEntry. I can see in my sample data that we have a date field, so I know that I’m going to need internationalization, owing to the cultural info needed to render a date. So, let’s get the basics in place with a using statement for the globalization package, a namespace, and the class. Visual Studio likes to mark the classes with an internal access modifier. Call me old-fashioned. I always change them to public, assuming that’s appropriate, and in this case, it is:

using System.Globalization;
namespace WebLogReporter
{
  public class NginxLogEntry
  {
     //TODO:  the rest of the code will go here
  }
}

With the basics out of the way, let’s set up our member variables. Aside from a couple of constructors, that’s really all we’ll need since this class is designed to represent the line entries in the log.  

The fields we’re interested in are visually identifiable in the preceding data sample:

  • ServerIPAddress represents the IP address of the web server from which the log was taken.
  • RequestDateTime represents the date and time of each request in the log.
  • Verb represents the HTTP verb or request method. We’ll be supporting four, though there are many more available.
  • Route represents the route of the request. Our sample is from a RESTful API.
  • ResponseCode represents the HTTP response code for the request. Successful codes are in the 200 and 300 range. Unsuccessful codes are in the 400 and 500 range.
  • SizeInBytes represents the size of the data returned by the request.
  • RequestingAgent represents the HTTP agent used to make the request. This is usually a reference to the web browser used, but in all the cases in our sample, it is a client written in Python 3 using the popular requests library.

In addition to our fields, I’ll start with an enum to store the four acceptable values for the HTTP methods, which I’ve called HTTPVerbs. The rest are represented with straightforward auto-properties:

    public enum HTTPVerbs { GET, POST, PUT, DELETE }
    public string ServerIPAddress { get; set; }
    public DateTime RequestDateTime { get; set; }
    public HTTPVerbs Verb { get; set; }
    public string Route { get; set; }
    public int ResponseCode { get; set; }
    public int SizeInBytes { get; set; }
    public string RequestingAgent { get; set; }

Now that I’ve got my enumeration and properties in place, I’m going to make a couple of constructors. I want one constructor that allows me to pass in a line from the log. The constructor will parse the line and return a fully populated class with the log line as an input. Here’s the top of the first constructor:

    public NginxLogEntry(String LogLine)
    {

First, I’ll take the log line being passed in and split it into a string array, using the .Split() method, which is part of the string class:

      var parts = LogLine.Split(' ');

While developing, I run into some corner cases. Sometimes, the log lines don’t have 12 fields, as I expect. To account for this, I add a conditional that detects log lines that come in with fewer than 12 parts. This rarely happens but when it does, I want to send them to the console so that I can see what is going on. This is the kind of thing you’d probably take out. I’m embracing my inner stovepipe developer here, so I’m leaving it in:

      if(parts.Length < 12)
      {
        Console.WriteLine(LogLine);
      }

Now, let’s set to work taking apart the line based on the split. It’s pretty easy to pick out the server IP address as the first element of the split array:

      ServerIPAddress = parts[0];

We don’t care about those two dashes in positions 1 and 2. We can see the date in the third position. Dealing with dates has always been slightly more fun than your average root canal. Think about all the formatting and the parsing needed just to get it into something we know will work with the database code that we’ll eventually write. Thankfully, C# handles this with aplomb. We pull out the date parts and we use a custom date format parser. I don’t really care about expressing the date in terms of locale, so I’ll use InvariantCulture as the second argument in the date parse:

      var rawDateTime = parts[3].Split(' ')[0].Substring(1).Trim();
      RequestDateTime = DateTime.ParseExact(rawDateTime, "dd/MMM/yyyy:HH:mm:ss", CultureInfo.InvariantCulture);

Next, we get to work parsing the HTTP verb. It needs to conform to the enum we defined at the top of the class. I start by pulling the relevant word and making sure it’s clean by giving it a quick trim. Then, I cast it to the enumeration type. I probably should have used tryParse(), but I didn’t. It still works with the input sample if I don’t, and that’s the kind of thinking that lands us in a stovepipe prison later:

      var rawVerb = parts[5].Trim().Substring(1); 
      Verb = (HTTPVerbs)Enum.Parse(typeof(HTTPVerbs), rawVerb); 

The Route value, the ResponseCode value, and the SizeInBytes value are just grabbed based on their position. In the latter two cases, I just used int.parse() to turn them into integers:

      Route = parts[6].Trim();
      ResponseCode = int.Parse(parts[8].Trim());
      SizeInBytes = int.Parse(parts[9].Trim());

Lastly, I need the RequestingAgent. The sample data has some pesky double quotes that I don’t want to capture, so I’ll just use the string.replace() method to replace them with null, effectively getting rid of them:

      RequestingAgent = parts[11].Replace("\"", null);
    }

I now have a very useful constructor that does my line parsing for me automatically. Nice!

My second constructor is more standard fare. I’d like to create NginxLogEntry by simply passing in all the relevant data elements:

    public NginxLogEntry(string serverIPAddress, DateTime 
    requestDateTime, string verb, string route, int 
    responseCode, int sizeInBytes, string requestingAgent)
    {
      RequestDateTime = requestDateTime;
      Verb = (HTTPVerbs)Enum.Parse(typeof(HTTPVerbs), 
              verb);
      Route = route;
      ResponseCode = responseCode;
      SizeInBytes = sizeInBytes;
      RequestingAgent = requestingAgent;
    }
  }
}

This class begins as all do – with property definitions. We have a requirement to store the log data in SQL Server. For this, I created a database on my laptop running SQL Server 2019. If you don’t have any experience with SQL Server, don’t worry. This is the only place it’s mentioned. You don’t need SQL knowledge to work with patterns in this book. I created a new database called WebLogEntries, then created a table that matches my object structure. The Data Definition Language (DDL) to create the table looks as follows:

CREATE TABLE [dbo].[WebLogEntries](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [ServerIPAddress] [varchar](15) NULL,
    [RequestDateTime] [datetime] NULL,
    [Verb] [varchar](10) NULL,
    [Route] [varchar](255) NULL,
    [ResponseCode] [int] NULL,
    [SizeInBytes] [int] NULL,
    [RequestingAgent] [varchar](255) NULL,
    [DateEntered] [datetime] NOT NULL,
    CONSTRAINT [PK_WebLogEntries] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
      IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
      ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = 
      OFF) ON [PRIMARY]
) ON [PRIMARY]

As you can see, I have added the ubiquitous auto-incrementing primary key field, simply called id. I also added a field to track when the record was entered and set its default value to SQL Server’s GETDATE() function, which yields the current date on the server.

Let’s move on to the code that reads and writes data with SQL Server. I think most people would use an Object-Relational Mapper (ORM) such as .NET’s EF for this. I prefer to leverage the control and performance I get from working directly with the database. In my IDE, I’ll create a second class called SQLServerStorage. If you’re following along, don’t forget to add the Systems.Data package via NuGet.

As before, I’ll start with the dependencies:

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;

Next, I’ll set up the class:

namespace WebLogReporter
{
  public class SQLServerStorage
  {
      //TODO:  the rest of the code goes here
  }
}

Unlike the data class we created earlier, this one is all about the methods. The first method I’ll make stores the data in the database using a direct connection. If you’ve only ever used EF, and you understand SQL (which you should), I highly recommend you try this style and test it for speed against your usual EF-driven code. You’ll see a huge difference, especially at scale. I’ll get off my proverbial soapbox now and get back to creating the StoreLogLine method. It takes in the NginxLogEntry class that we just wrote as its sole input:

    public void StoreLogLine(NginxLogEntry entry)
    {

Next, let’s connect to the database. I use the using syntax for this. If you’ve not used this before (see what I did there?), it’s very convenient since it handles the timely closure and destruction of whatever you create. In this case, I’m creating a database connection. Even in throwaway code, there are things you just don’t do, such as open a connection to a resource and fail to close it. It’s just so rude! This line sets up my connection. I also recommend a strong database password. As usual, I can hide behind the excuse that it’s throwaway code. At this point, I’ve likely repeated this more times than your local government has told you to wear a mask. And as with your local government, it probably won’t be the last time you hear it:

      using (SqlConnection con = new 
             SqlConnection("Server=Localhost;Database=
                           WebLogReporter;User 
                           Id=SA;Password=P@ssw0rd;"))
      {

Next, I’ll build my Data Manipulation Language (DML) statement for inserting data into the database using the connection we just forged. I’ll use the StringBuilder class, which is part of System.Text:

       var sql = new StringBuilder("INSERT INTO 
                [dbo].[WebLogEntries] (ServerIPAddress, 
                RequestDateTime, Verb, Route, ResponseCode, 
                SizeInBytes, RequestingAgent) VALUES (");
        sql.Append("'" + entry.ServerIPAddress + "',");
        sql.Append("'" + entry.RequestDateTime + "', ");
        sql.Append("'" + entry.Verb + "', ");
        sql.Append("'" + entry.Route + "', ");
        sql.Append(entry.ResponseCode.ToString() + ", ");
        sql.Append(entry.SizeInBytes.ToString() + ", ");
        sql.Append("'" + entry.RequestingAgent + "')");

Next, let’s open the connection, then execute our SQL statement:

        con.Open();
        
        using(SqlCommand cmd = con.CreateCommand())
        {
          cmd.CommandText = sql.ToString();
          cmd.CommandType = System.Data.CommandType.Text;
          cmd.ExecuteNonQuery();
        }
      
      }
    }

Fabulous! Now that we’re writing data, it stands to reason that we should also read it. Otherwise, our class would be really cruddy. Or maybe it wouldn’t be? I’ll let you mull that over while I type out the next method signature:

    public List<NginxLogEntry> RetrieveLogLines()
    {

The read method is going to return a list of NginxLogEntry instances. This is why we made that second constructor in the NginxLogEntry class earlier. I’ll start by instantiating an empty list to use as the return value. After that I’ll make a really simple SQL statement to read all the records from the database:

      var logLines = new List<NginxLogEntry>();
      var sql = "SELECT * FROM WebLogEntries";

Using the same using syntax as before, I’ll open a connection and read the records:

      using (SqlConnection con = new 
            SqlConnection("Server=Localhost;Database=
            WebLogReporter;User Id=SA;Password=P@ssw0rd;"))
      {
        SqlCommand cmd = new SqlCommand(sql, con);
        con.Open();
        SqlDataReader reader = cmd.ExecuteReader();

With the select statement executed, I’ll use a reader to get the data out line by line, and for each record, I’ll instantiate a NginxLogEntry class. Since it’s supposed to be prototype code, I’m relying on the positions in the dataset for data retrieval. This is not uncommon, but it is fairly brittle. A restructuring of the table will break this code later. But it’s throwaway code! See? I told you that you’d hear it again:

        while (reader.Read())
        {
          var serverIPAddress = reader.GetString(1);
          var requestDateTime = reader.GetDateTime(2);
          var verb = reader.GetString(3);
          var route = reader.GetString(4);
          var responseCode = reader.GetInt32(5);
          var sizeInBytes = reader.GetInt32(6);
          var requestingAgent = reader.GetString(7);
          var line = new NginxLogEntry(serverIPAddress, 
                     requestDateTime, verb, route, 
                     responseCode, sizeInBytes, 
                     requestingAgent);

Now that I’ve constructed the object using the data from the table, I’ll add it to my logLines list and return the list. The using statement handles the closure of all the database resources that I created along the way:

          logLines.Add(line);
        }
      }
      return logLines;
    }
  }
}

To sum it up, the class has two methods. The first, StoreLogLine, takes an instance of the NginxLogEntry class and converts the data into a SQL statement compatible with our table structure. We then perform the insert operation. Since I used the using syntax to open the connection to the database, that connection is automatically closed when we leave the scope of the method.

The second operation works in reverse. RetrieveLogLines executes a select statement that retrieves all our data from the table and converts it into a list of NginxLogEntry objects. The list is returned at the close of the method.

The last component is the output component. The class is called Report. Its job is to convert the records requested from the database into an HTML table, which is then written to a file.

I’ll set up the class with the dependencies and begin the class with the usual setup:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebLogReporter
{
  public class Report
  {
      //TODO: the rest of your code goes here
  }

Next, I’ll add the method to generate the report:

    public void GenerateReport(string OutputPath)
    {

I’ll now use the SQLServerStorage class that we made earlier:      

var database = new SQLServerStorage();
var logLines = database.RetrieveLogLines();

I have the data. Now, I’ll use another StringBuilder to generate the HTML. It’s table code because this is absolutely not a book on frontend design:

      var output = new 
                   StringBuilder("<html><head><title>Web 
                   Log Report</title></head><body>");
      output.Append("<table><tr><th>Request 
                     Date</th><th>Verb</th><th>Route</th>
                     <th>Code</th><th>Size</th><th>Agent
                     </th></tr>");
      foreach (var logLine in logLines)
      {
        output.Append("<tr>");
        output.Append("<td>" + 
            logLine.RequestDateTime.ToString() + "</td>");
        output.Append("<td>" + logLine.Verb + "</td>");
        output.Append("<td>" + logLine.Route + "</td>");
        output.Append("<td>" + 
            logLine.ResponseCode.ToString() + "</td>");
        output.Append("<td>" + 
            logLine.SizeInBytes.ToString() + "</td>");
        output.Append("<td>" + 
            logLine.RequestingAgent.ToString() + "</td>");
        output.Append("</tr>");
      }
      output.Append("</table></body></html>");

Finally, we have a wonderful C# one-liner to output the file so that it’s ready for viewing in your favorite browser:

     File.WriteAllText(OutputPath, output.ToString());
    }
  }
}

It might be ugly, but it works. I’ll say it once again just because I can. It’s throwaway code! One trick I advocate when writing throwaway code is to make it so incredibly ugly that nobody in their right mind would put their name to it. I think I’ve accomplished that here. I just used a string builder to create my HTML. No spaces or formatting. It’s basically a minified HTML file, which is, of course, an intended feature and not at all inspired by laziness.

There’s one last thing to do before we put this baby to bed. We need to edit the Program.cs file Visual Studio created as the project’s entry point. This file glues all the other pieces together. The most recent editions of most C# IDEs generate the entry point for console apps within the Program.cs file. This isn’t new. What is new is the format this file takes. The new format lacks the usual constructor and class setup we’ve seen so far in the classes we created from scratch. Behind the scenes, the compiler is generating those definitions for us, but it makes Program.cs look different from everything else. Rather than present all the usual boilerplate, it’s straight to business.

We’ll start by using the WebLogReporter class that we just created:

using WebLogReporter;

We’ll do a perfunctory and minimal test to make sure the right number of arguments were passed in from the command line. We need a path to the log file and an output path. If you don’t pass in the right number of arguments, we’ll give you a little command-line hint, then exit with a non-zero code, in case this is part of some sequence of automation. I know, it’s throwaway code, but I’m not a barbarian:

if (args.Length < 2)
{
  Console.WriteLine("You must supply a path to the log file 
    you want to parse as well as a path for the output.");
  Console.WriteLine(@"For example: WebLogReporter 
    c:\temp\nginx-sample.log c:\temp\report.html");
  Environment.Exit(1);
}

Now, we check whether the log input file exists. If it doesn’t, we alert the user to our disappointment and again exit with non-zero code:

if (!File.Exists(args[0]))
{
  Console.WriteLine("The path " + args[0] + " is not a 
    valid log file.");
  Environment.Exit(1);
}

If they make it this far, we’re assuming everything is good and we get to work:

var logFileName = args[0];
var outputFile = args[1];
Console.WriteLine("Processing log: " + logFileName);
int lineCounter = 0;

We instantiate SQLServerStorageClass so we can store our records as we read them in:

var database = new SQLServerStorage();

Now, we open the input log file, and with a foreach loop, we take each line and use our parsing constructor in NginxLogEntry to create an NginxLogEntry object. We then feed that to our database class. If we encounter a line that’s a problem, we write out a message that states where the problem occurred so that we can review it later:

foreach(string line in 
        System.IO.File.ReadLines(logFileName))
{
  lineCounter++;
  try
  {
    var logLine = new NginxLogEntry(line);
    database.StoreLogLine(logLine);
  }
  catch 
  { 
    Console.WriteLine("Problem on line " + lineCounter); 
  }
}

We’ve parsed the log data and written it to the database. All that’s left is to use the Report class to write out our HTML:

var report = new Report();
report.GenerateReport(outputFile);
Console.WriteLine("Processed " + lineCounter + " log 
                  lines.");

To sum up, the Program.cs file contains the main program. The current version of C# allows us to dispense with the usual class definition in the main file for the project.

First, we check to make sure the user entered two arguments. It’s hardly a bulletproof check, but it’s good enough to demo.

Next, after making sure the input log file is a legitimate path, we open the file, read it line by line, and save each line to the database.

Once we’ve read all our lines, we read back the data from the database and convert it to HTML using the report object.

Your program is complete; you demonstrate it to the customer, and they are delighted! A week later, their boss has lunch with your boss, and a new requirement comes in stating the client would now like to support logs from two other web server formats: Apache and IIS. They’d also like to select the output from several different formats, including the following:

  • HTML (which we have already delivered)
  • Adobe PDFs
  • Markdown
  • JavaScript Object Notation (JSON)

The purpose of the last format, JSON, is that it allows outside clients to ingest that data into other systems for further analysis, such as capturing trend data over time.

While these concise descriptions of the requirements are hardly what we’d want when building a real extension to our program, they are enough to get you thinking.

What would you do?

Have we built a stovepipe system? If not, is there a chance it might evolve into one? Pause for a moment and think about this before reading further.

I believe we have built a stovepipe system. Here’s why:

  • Our classes are all single-purpose and coupled directly to the web server log formats
  • Our software is directly coupled with SQL Server
  • Our output to HTML is the only possible output, given that we didn’t create an interface or structure to abstract the output operation and format

You might be thinking that the second set of requirements was unknown at the time we created our first release. That’s accurate. You might further defend your idea by stating you are not psychic, and there’s no way you could have known you would need to extend the program per the second set of requirements. You’re right there too. Nobody is prescient. But despite that, you know, if for no other reason than you’ve read this chapter so far, that any successful program must support extension. This is true because you know your first iteration has now begotten a request for a second and that always entails changes and additions to the requirements. We can never know how the requirements will change, but we do know that they will change.

How can patterns help?

These factors are undoubtedly part of your professional life right now. Perhaps only a few of them are at play at a time. If you stay in software development for any length of time, you’ll undoubtedly encounter them all at some point. Remember, everything we’ve talked about so far is an antipattern. All this negative energy needs a counterbalance. You might even be tempted to say that you need to bring balance to the force. Instead of becoming a dark Jedi, maybe something less radical will do. You can learn to use patterns to balance and ultimately defeat the antipatterns and the forces that create and enable them.

I think it’s time we formally introduced the concept that I have left to your imagination thus far. I could offer my own definition of patterns but I’d rather stand on the shoulders of giants. From the days since Grace Hopper logged the first bug on the Mark II at Harvard in 1947 (see Figure 1.4), programmers and computer scientists have been facing the same problems over and over again. We get a little bit smarter every time and we write down what we did. If you take the distilled experience and knowledge gleaned through hard-won trial and error over the course of seven decades, from the early wartime pioneers to the most recent graduates, you wind up with a set of patterns, which are descriptions of solutions to consistently recurring problems.

Figure 1.4 – The very first computer bug was literally a bug (moth) that had crawled into the relays of the Mark II computer at Harvard University

Figure 1.4 – The very first computer bug was literally a bug (moth) that had crawled into the relays of the Mark II computer at Harvard University

The idea of patterns originated in the field of architecture, that is, traditional architecture, with respect to the design and creation of buildings. In 1977, Christopher Alexander documented a pattern language designed to form the basis for the best practices for building towns. The book describes two hundred and fifty-three patterns presented as the paragon of architectural design. The book breaks everything down into objects. I find it fascinating that the language doesn’t even change when you adapt the seminal book on architectural patterns to our explanation of software patterns. I’d characterize the book as being the synthesis of a language used to describe objects in the real world and how to mold spaces and objects together to achieve harmony. As with the motto of the Vulcans in the TV and film franchise Star Trek, the goal of the language of patterns is infinite diversity expressed in infinite combinations. Alexander himself describes a pattern as a solution to problems that occur frequently enough that every practitioner will likely recognize them. He then describes a solution in a way that isn’t directly tied to any implementation. Keeping it flexible in this way, the same solution may be used on a million projects and in a million slightly different ways.

Let’s shift focus from the world of building architecture to the realm of software architecture. We revisit the famous GoF. They define a design pattern as an abstraction of a recurring problem that pinpoints the chief elements of design structure, focusing on the idea of creating reusable object-oriented software.

Patterns are going to be the weapon that we can use to overcome the antipatterns and dark forces that prevail in both the loftiest corporate institutions and most hallowed halls in small business.

Are you ready to fight the darkness? Roll up your sleeves and let’s get to work!

Summary

This chapter initiated our discussion of patterns by defining antipatterns.  

We learned that if we design software only with a mind toward meeting the requirements, we will build a system that can’t be extended easily. These systems are called Stovepipe systems because over time, they degenerate structurally as an exhaust vent on a coal-burning stove does. You will invariably reach a point where maintaining and extending such a system is untenable. Nobody wants to be the one to tell the bosses that you need six months with no new releases so that you can rebuild the company’s cash-cow product. Designing with patterns will help you avoid these kinds of pitfalls.

We also learned about a similar antipattern called the Big Ball of Mud. Foote and Yoder described the prevailing forces that we all recognize in our daily work lives: time, cost, experience, skill, visibility, complexity, change, and scale. These forces confound our ability to write good code in the first place. Even if we can write good code for our first release, these forces erode systems over time, such as a tiny stream forming the Grand Canyon.

We saw an example of throwaway code, which is how most projects are born. We now know what bad practice it is to just clean up the throwaway code and then ship it. It pays to spend the time to architect projects properly and assume the worst-case scenario: that your software will be wildly successful. If it is, you can absolutely count on feature requests and new requirements that you never even imagined when you wrote the throwaway prototype.

Patterns can be thought of as a language that defines common software design elements, coupled with an abstract solution that can be implemented in many different ways. They aren’t tied to a particular language or technology stack. As you learn to use patterns, your software will become more robust owing to a stronger foundation. Your projects will be able to support your inevitable success by providing avenues for future features and expansion that you couldn’t have conceived of when you started your project.

In the next chapter, I’ll prepare you for your patterns journey with the C# programming language. I will cover some popular idioms and practices in relation to object-oriented programming. In particular, pay close attention to the presentation of SOLID methodology, because it is the foundation upon which successful patterns are implemented.

If you are new, or perhaps returning to C# having worked with other object-oriented languages, I want to direct you to Appendix 1 at the end of the book. I wrote this for my students and colleagues who have an interest in learning about patterns but have focused on other languages, such as JavaScript or Python. Patterns are language-agnostic. Learning these patterns exclusive of a language is possible. However, I believe that we all learn best by doing, and that means you need an implementation language. The neat thing about programming languages is that they are pretty much all the same. All of them use variables, objects, methods, collections, and loops.  

In Appendix 1, I’ll cover the object-oriented features of the C# language. I’ve alluded to my idea that newer developers within the field are the most at risk in terms of knowing the language reasonably well but simultaneously clinging to Golden Hammers and using C# to produce stovepipe software. I have a great many students who learn JavaScript from me and who, at my strong encouragement, want to take the next steps in their journey by learning C#. Given how differently inheritance and common structures such as objects and classes work between the two languages, you’ll undoubtedly detect my desire to be inclusive. I had originally written this appendix to be Chapter 2, but I didn’t want to bear the expense of having the C# crowd cry out, “I’m BORED.” Even if you’re a C# veteran, I encourage you to peruse the chapter. You might find I’ve explained things a little differently from many other authors, and certainly differently from an academic textbook.

Questions

  1. What, in your own words, is a pattern? What is an antipattern?
  2. What antipatterns have you seen in your own work before?
  3. What is a stovepipe system? Can you point to an example from your own body of work? Don’t worry, I won’t tell.
  4. Can you think of a time when you wielded a Golden Hammer? What was the hammer and what did you perceive as the nails?

Further reading

  • Alexander, C. (1977). A pattern language: towns, buildings, construction. Oxford University Press
  • Brown, W. H., Malveau, R. C., McCormick, H.W. S., & Mowbray, T. J. (1998). AntiPatterns: refactoring software, architectures, and projects in crisis. John Wiley & Sons, Inc.
  • Foote, B., & Yoder, J. (1997). Big Ball of Mud. Pattern languages of program design, 4, 654–692. Retrieved from http://www.laputan.org/pub/foote/mud.pdf
  • Gamma, E., Helm, R., Johnson, R., Vlissides, J., & Patterns, D. (1995). Elements of reusable object-oriented software (Vol. 99.) Reading, Massachusetts: Addison-Wesley
  • Johnson, J. (1995). Creating Chaos. American Programmer, July 1995
  • Nazeer, H. (2020). A pattern language: towns, buildings, construction (review). Journal of Research in Architecture and Planning, Vol. 29, Second Issue
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Recognize solutions to common problems in software design with C#
  • Explore real-world applications of design patterns that can be used in your everyday work
  • Get to grips with 14 patterns and their design implementations

Description

As a software developer, you need to learn new languages and simultaneously get familiarized with the programming paradigms and methods of leveraging patterns, as both a communications tool and an advantage when designing well-written, easy-to-maintain code. Design patterns, being a collection of best practices, provide the necessary wisdom to help you overcome common sets of challenges in object-oriented design and programming. This practical guide to design patterns helps C# developers put their programming knowledge to work. The book takes a hands-on approach to introducing patterns and anti-patterns, elaborating on 14 patterns along with their real-world implementations. Throughout the book, you'll understand the implementation of each pattern, as well as find out how to successfully implement those patterns in C# code within the context of a real-world project. By the end of this design patterns book, you’ll be able to recognize situations that tempt you to reinvent the wheel, and quickly avoid the time and cost associated with solving common and well-understood problems with battle-tested design patterns.

Who is this book for?

This book is for beginner and mid-level software developers who are looking to take their object-oriented programs or software designing skills to the next level by learning to leverage common patterns. A firm grasp of programming fundamentals and classical object-oriented programming (OOP) using languages like C#, C++, Objective-C, or Java is expected.

What you will learn

  • Get to grips with patterns, and discover how to conceive and document them
  • Explore common patterns that may come up in your everyday work
  • Recognize common anti-patterns early in the process
  • Use creational patterns to create flexible and robust object structures
  • Enhance class designs with structural patterns
  • Simplify object interaction and behavior with behavioral patterns
Estimated delivery fee Deliver to Czechia

Premium delivery 7 - 10 business days

€25.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Oct 14, 2022
Length: 442 pages
Edition : 1st
Language : English
ISBN-13 : 9781803242736
Category :
Languages :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Czechia

Premium delivery 7 - 10 business days

€25.95
(Includes tracking information)

Product Details

Publication date : Oct 14, 2022
Length: 442 pages
Edition : 1st
Language : English
ISBN-13 : 9781803242736
Category :
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 99.97 111.97 12.00 saved
Apps and Services with .NET 7
€37.99
C# 11 and .NET 7 – Modern Cross-Platform Development Fundamentals
€25.99 €37.99
Real-World Implementation of C# Design Patterns
€35.99
Total 99.97 111.97 12.00 saved Stars icon
Banner background image

Table of Contents

13 Chapters
Part 1: Introduction to Patterns (Pasta) and Antipatterns (Antipasta) Chevron down icon Chevron up icon
Chapter 1: There’s a Big Ball of Mud on Your Plate of Spaghetti Chevron down icon Chevron up icon
Chapter 2: Prepping for Practical Real-World Applications of Patterns in C# Chevron down icon Chevron up icon
Part 2: Patterns You Need in the Real World Chevron down icon Chevron up icon
Chapter 3: Getting Creative with Creational Patterns Chevron down icon Chevron up icon
Chapter 4: Fortify Your Code With Structural Patterns Chevron down icon Chevron up icon
Chapter 5: Wrangling Problem Code by Applying Behavioral Patterns Chevron down icon Chevron up icon
Part 3: Designing New Projects Using Patterns Chevron down icon Chevron up icon
Chapter 6: Step Away from the IDE! Designing with Patterns Before You Code Chevron down icon Chevron up icon
Chapter 7: Nothing Left but the Typing – Implementing the Wheelchair Project Chevron down icon Chevron up icon
Chapter 8: Now You Know Some Patterns, What Next? Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.1
(9 Ratings)
5 star 66.7%
4 star 11.1%
3 star 0%
2 star 11.1%
1 star 11.1%
Filter icon Filter
Top Reviews

Filter reviews by




Dilip Apr 06, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Wonderful read.
Subscriber review Packt
Milan Frydrych Nov 30, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
One of the best books on design patterns.
Feefo Verified review Feefo
Bruce Van Horn Jul 02, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I might be a little biased though.
Amazon Verified review Amazon
Tiny Nov 23, 2022
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This has to be one of the most instructive works on design patterns I have seen in a while. If your code lacks structure, you struggle with implementation, and your throwaway code keeps increasing, “Real-World Implementation of C# Design Patterns” (Packt) by Bruce M. Van Horn II should move to the top of your list. The book works through a fictional story about sisters running a bike factory to pair software patterns to real-world manufacturing instances, making both the code and the implementation clear in every case. The first section covers common mistakes and an approach to C#, while the next section builds through implementing those patterns. Finally, the last two appendices, one on Unified Modelling Language (UML) and one on C# basics, are well worth the admission cost. Van Horn starts with anti-patterns using the terms stovepipe, ball of mud, and golden hammer. All developers have run across these instances, but they are extensively detailed. Stovepipes detail a single approach to reach coding goals, a ball of mud is randomly adding code until works correctly, and a golden hammer shows when a single approach appears for all solutions. Each can impede code and prevent success. These are then paired with spaghetti, lasagna, and ravioli terms for code in the next chapter. Spaghetti code is the typical approach to throwing everything in the bowl and being done. Lasagna shows the extensive use of layers to prevent different items from interacting in a non-desired fashion. Finally, ravioli moves to almost a container fashion, where each coding element is held separate but can be orchestrated through a common sauce. The next section shows creational, structural and behavioral patterns in some detail. These patterns are some of those originated by the original Gang of Four documentation but each is presented within the bicycle factory fictional construct. Along the way, each gets referenced by how well they meet the traditional SOLID basis for software architecture. If you haven’t looked at SOLID, it stands for single responsibility, open-closed, Liskov substitution, Interface segregation, and dependency inversion. Even if you regularly meet those goals in your own programming, this can provide some useful insight into making those achievements even more effective. Finally, in the words of Monty Python, the third part offers something completely different. This section becomes an entirely requirements-focused discussion using UML to show how one can model code and potential patterns before ever touching a keyboard. More coders using this approach would likely be beneficial to the industry as a whole. However, it doesn’t stop with just the modeling as the following chapter describes a step-by-step how to in putting all those code elements into your program. The conversion to one to the other is an outstanding critical thinking exercise in structuring an approach and then making it work. In the last chapter, the author shows the patterns he skipped, explains when each might be used, and offers a lesson in building one’s own patterns. Normally, I would skip the appendices but not in this case. Most programming books open with a syntax and language discussion. While useful for new programmers, for those with some experience, this can often derail the entire book. Putting this section at the end allows easy reference if you need it but allows the reader to focus on the meat of implementing various patterns. The second appendix then walks through using UML to assist your coding. If you haven’t used UML, you probably have seen it in project diagrams and it is always worth going back to those basics. After all, the clearer the model, the more efficient your programming will be. All in all, “Real World Implementation of C# Design Patterns” by Bruce Van Horn II is a must-have for your desk reference. This is the best book on design patterns I have seen in a while. While the title says C#, and the author focuses on that code, with some minor syntax changes, it could just as easily be used for a variety of languages such as Java, Python, Go, Ruby, or a variety of others. If you do any type of extensive programming, I would move this book to the top of your reading list.
Amazon Verified review Amazon
Amazon Customer Oct 30, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I was dreading reading a book about Design Patterns but as a long time c# developer, I wanted to up my game, to make sure I was doing things the right way and be able to answer interview questions with a full understanding of patterns and to no longer bluff it.This book is a massively relatable journey and explanation to give you a feel for patterns rather than it being the academic chore that I was expecting. It has real passion for what might have otherwise been a very boring topic and goes above and beyond. I may have been practising some of the aspects such as SOLID principles without knowing it but this cements my full understanding of the terms.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact [email protected] with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at [email protected] using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on [email protected] with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on [email protected] within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on [email protected] who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on [email protected] within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela