Archive for September, 2015

The Observer Pattern

Author: Cole Francis, Architect

Click here to download my solution on GitHub

BACKGROUND

If you read my previous article, then you’ll know that it focused on the importance of software design patterns. I called out that there are some architects and developers in the field who are averse to incorporating them into their solutions for a variety of bad reasons. Regardless, even if you try your heart out to intentionally avoid incorporating them into your designs and solutions, the truth of the matter is you’ll eventually use them whether you intend to or not.

A great example of this is the Observer Pattern, which is arguably the most widely used software design pattern in the World. It comes in a number of different styles, with the most popular being the Model-View-Controller (MVC), whereby the View representing the Observer and the Model representing the observable Subject. People occasionally make the mistake of referring to MVC as a design pattern, but it actually an architectural style of the Observer Design Pattern.

The Observer Design Pattern’s taxonomy is categorized in the Behavioral Pattern Genus of the Software Design Pattern Family because of its object-event oriented communication structure, which causes changes in the Subject to be reflected in the Observer. In this respect, the Subject is intentionally kept oblivious, or completely decoupled from the Observer class.

Some people also make the mistake of calling the Observer Pattern the Publish-Subscribe Pattern, but they are actually two distinct patterns that just so happen to share some functional overlap. The significant difference between the two design patterns is that the Observable Pattern “notifies” its Observers whenever there’s a change in the Observed Subject, whereas the Publish-Subscribe Pattern “broadcasts” notifications to its Subscribers.

A COUPLE OF NEGATIVES

As with any software design pattern, there are some cons associated with using the Observer Pattern. For instance, the base implementation of the Observer Pattern calls for a concrete Observer, which isn’t always practical, and it’s certainly not easily extensible. Building and deploying an entirely new assembly each time a new Subject is added to the solution would require a rebuild and redeployment of the assembly each time, which is a practice that many larger, bureaucratically-structured companies often frown upon. Given this, I’ll show you how to get around this little nuance later in this article.

Another problem associated with the Observer Pattern involves the potential for memory leaks, which are also referred to as “lapsed listeners” or “latent listeners”. Despite what you call it, a memory leak by any other name is still a memory leak. Regardless, because an explicit registering and unregistering is generally required with this design pattern, if the Subjects aren’t properly unregistered (particularly ones that consume large amounts of memory) then unnecessary memory consumption is certain, as stale Subjects continue to be needlessly observed until something changes. This can result in performance degradation. I’ll explain to you how you can work around this issue.

OBSERVER DESIGN PATTERN OVERVIEW

Typically, there are three (3) distinct classes that comprise the heart and soul of the Observer design pattern, and they are the Observer class, the Subject class, and the Client (or Program). Beyond this, I’ve seen the pattern implemented in a number of different ways, and asking a roomful of architects how they might go about implementing this design pattern is a lot like asking them how they like their morning eggs. You’ll probably get a variety of different responses back.

However, my implementation of this design pattern typically deviates from the norm because I like to include a fourth class to the mix, called the LifetimeManager class. The purpose of the LifetimeManager class is to allow each Subject class to autonomously maintain its own lifetime, alleviating the need for the client to explicitly call the Unregister() method on the Subject object. It’s not that I don’t want the client program to explicitly call the Subscriber’s Unregister() method, but this cleanup call does occasionally get omitted for whatever reason. So, the inclusion of the LifeTimeManager class provides an additional safeguard to protect us against this. I’ll focus on the LifetimeManager class a little bit later in this article.

Moving on, the Observer design pattern is depicted in the class diagram below. As you can see, the Subject inherits from the LifetimeManager class and implements the ISubject interface, but the client program and the Observer are left decoupled from the Subject. You will also notice that the Subject provides the ability to allow a program to register and unregister a Subject class. By inheriting from the LifetimeManager class, the Subject class now also allows the client to establish specific lifetime requirements for the Subject class, such as whether it uses a basic or sliding expiration, its lifetime in seconds, minutes, hours, days, months, and even years. And, if the developer fails to provide this information through the Subject’s overloaded constructor, then the default constructor provides some default values to make sure the Subject is cleaned up properly.

ClassDiagram2

A MORE DETAILED EXPLANATION OF THE PATTERN

The Subject Class

The Subject class also contains a Change() method that’s exactly like the Register() method. This is something else that’s not normally a part of this design pattern, but I intentionally added this because I don’t think it makes sense to call the Register() method anytime changes are made to the Subject(). I think it makes for a bad developer experience. Instead, registering the Subject object once and then calling the Change() method anytime there are changes to the Subject object makes much more sense in my opinion. We can impose the cleanup work upon the Observer class each time the Subject object is changed.

The Observer Class

The Observer class includes an Update() method, which accepts a Subject object and the operation the Observer class needs to perform on the Subject object. For instance, if there’s an add or an update to the Subject object, then the Observers searches through its observed Subject cache to find it using it’s unique SubscriptionId and CacheId’s. If the Subject exists in the cache, then the Observer updates it by deleting the old Subject and adding the new one. If it doesn’t find it in the Subject cache, then it simply adds it. The Observer also accepts a remove action, which causes it to remove the Subject from it observed state.

The Client Program

The only other important element to remember is that anytime an action takes place, then notifications are always propagated back to the client program so that it’s always aware of what’s going on behind the scenes. When the Subject is registered, the client program is notified; When the Subject is unregistered, the client program is notified; When the observed Subject’s data changes, the client program is notified. One of the important tenets of this design pattern is that the client program is always kept aware of any changes that occur to its observed Subject.

The LifetimeManager Class

The LifetimeManager class, which is my own creation, is responsible for maintaining the lifetime of each Subject object that gets created. So, for every Subject object that gets created, a LifetimeManager class also gets created. The LiftetimeManager class includes a variety of malleable properties, which I’ll go over shortly. Also keep in mind that these properties get set by the default constructor of the Subject() class, and they can also be overridden when the Subject object first gets created by passing the override values in an overloaded constructor that I provide in my design, or they can be overridden by simply changing any one of the LifetimeManager class’s property values and then calling the Subject’s Change() method. It’s really as simple as that. Nevertheless, here are the supported properties that make up the LifetimeManager class:

1. ExpirationType: Tells the system whether the expiration type is either basic or sliding. Basic expiration means that the Subject expires at a specific point in time. Sliding expiration simply means that anytime a change is made to the Subject, the expiration slides based upon the values you provide.

2. ExpirationValue: This is an integer that is relative to the next property, TimePrecision.

3. TimePrecision: This is an enumeration that includes specific time intervals, like seconds, minutes, hours, days, months, and even years. So, if I provide a 30 for TimePrecision and provide the enumTimePrecision.Minutes for TimePrecision, then this means that I want my data cache to automatically expire, and hence self-unregister, in 30 minutes. What’s more, if you fail to provide me with these values during the time you Register() your Subject, then I default them for you in the default Constructor code in the Subject class.

So, now that you have an overview and visual understanding of the Observer pattern class structure and relationships, I’ll now spend a little time going over my implementation of the pattern by sharing my working source code with you. My intention is that you can use my source code to get your very own working model up and running. This will allow you to experiment with the pattern on your own. It would also be nice to get some feedback regarding how well you think my custom LifetimeManager class helps to avoid unwanted memory leaks by providing each Subject class with the ability to maintain its own lifetime.

THE OBSERVER CLASS SOURCE CODE

For the most part, it’s the responsibility of the Observer class to perform update operations on a given Subject when requested. Furthermore, the Observer class should respect and observe any changes to the stored Subject’s lifecycle until the Subject requests the Observer to unregister it. Here’s my working example of the Observer class:


using System;
using System.Collections.Generic;


namespace ObserverClient
{
    /// 
    /// The abstract observer base class
    /// 
    public static class Observer
    {
        #region Member Variables

        /// 
        /// The global data cache
        /// 
        private static List _data = new List();

        #endregion

        #region Methods

        /// 
        /// Provides CRUD operations on the global cache object
        /// 
        internal static bool Update(LifetimeManager data, Enums.enumSubjectAction action)
        {
            try
            {
                object o = new object();

                // This locks the critical section, just in case a timer even fires at the same
                // time the main thread's operation is in action.
                lock (o)
                {
                    switch (action)
                    {
                        case Enums.enumSubjectAction.AddChange:
                            {
                                // Finds the original object and removes it, and then it re-adds it to the list
                                _data.RemoveAll(a => a.SubscriptionId == data.SubscriptionId && a.CacheData == data.CacheId);
                                _data.Add(data);
                                break;
                            }
                        case Enums.enumSubjectAction.RemoveChild:
                            {
                                // Finds the entry in the list and removes it
                                _data.RemoveAll(a => a.SubscriptionId == data.SubscriptionId && a.CacheData == data.CacheId);
                                break;
                            }
                        case Enums.enumSubjectAction.RemoveParent:
                            {
                                // Finds the entry in the list and removes it
                                _data.RemoveAll(a => a.SubscriptionId == data.SubscriptionId);
                                break;
                            }
                        default:
                            {
                                // This is useless
                                break;
                            }
                    }

                    return true;
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        #endregion
    }
}

THE SUBJECT CLASS SOURCE CODE

Once again, the intent of the Subject class is to expose methods to the client allow for the registering and unregistering of the observable Subject. It’s the responsibility of the Subject to call the Observer class’s Update() method and request that specific actions be taken on it (e.g. add or remove).

In my code example below, the Observer class acts as a storage cache for observed Subjects, and it also provides some basic operations necessary to adequately maintain the observed Subjects.

As a side note, take a look at the default and overloaded constructors in the Subject class, below. It’s in these two areas of the Subject object that I either automatically control or allow the developer to override the Subject’s lifetime. Once the lifetime of the Subject object expires, then it is unregistered in the Observer and the client program is then automatically notified that the subject was removed from observation.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ObserverClient.Interface;


namespace ObserverClient
{
    /// 
    /// This is the Subject Class, which provides the ability to register and unregister and object.
    /// 
    public class Subject : LifetimeManager,  ISubject
    {
        #region Events

        /// 
        /// Handles the change notification event
        /// 
        public event NotifyChangeEventHandler NotifyChanged;

        #endregion
        
        #region Methods

        /// 
        /// The delegate for the NotificationChangeEventHandler event
        /// 
        public delegate void NotifyChangeEventHandler(T notifyinfo, Enums.enumSubjectAction action);

        /// 
        /// The register method.  This adds the entry and data to the Observer's data cache
        /// and then provides notification of the event to the caller if it's successfully added.
        /// 
        public void Register()
        {
            try
            {
                if (Observer.Update(this, Enums.enumSubjectAction.AddChange))
                {
                    this.NotifyChanged(this, Enums.enumSubjectAction.AddChange);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// 
        /// The unregister method.  This removes the entry and data in the Observer's data cache
        /// and then provides notification of the event to the caller if it's successfully removed.
        /// 
        public void Unregister()
        {
            try 
	        {	        
		        if (this.SubscriptionId != null && this.CacheId == null)
                {
                    Observer.Update(this, Enums.enumSubjectAction.RemoveParent);
                    this.NotifyChanged(this, Enums.enumSubjectAction.RemoveParent);
                }
                else if (this.SubscriptionId != null && this.CacheId != null)
                {
                    Observer.Update(this, Enums.enumSubjectAction.RemoveChild);
                    this.NotifyChanged(this, Enums.enumSubjectAction.RemoveChild);
                }
	        }
	        catch (Exception)
	        {
		        throw;
	        }
        }

        /// 
        /// The change method.  This modifies the entry and data to the Observer's data cache
        /// and then provides notification of the event to the caller if successful.
        /// 
        public void Change()
        {
            try
            {
                if (Observer.Update(this, Enums.enumSubjectAction.AddChange))
                {
                    if(this.ExpirationType == Enums.enumExpirationType.Sliding)
                    {
                        this.ExpirationStart = DateTime.Now;
                        this.MonitorExpiration();
                    }

                    this.NotifyChanged(this, Enums.enumSubjectAction.AddChange);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// 
        /// The event handler for object expiration notifications. It calls unregister for the current object.
        /// 
        void s_ExpiredUnregisterNow()
        {
            // Unregisters itself
            this.Unregister();
        }

        #endregion

        #region Constructor(s)

        /// 
        /// The Subject's default constructor (i.e. all the values relating to cache expiration are defaulted to 1 minute).
        /// 
        public Subject()
        {
            this.ExpirationType = Enums.enumExpirationType.Basic;
            this.ExpirationValue = 1;
            this.TimePrecision = Enums.enumTimePrecision.Minutes;
            this.ExpirationStart = DateTime.Now;

            this.NotifyObjectExpired += s_ExpiredUnregisterNow;
            this.MonitorExpiration();
        }

        /// 
        /// The overloaded Subject constructor
        /// 
        public Subject(Enums.enumExpirationType expirationType, int expirationValue, Enums.enumTimePrecision timePrecision)
        {
            this.ExpirationType = expirationType;
            this.ExpirationValue = expirationValue;
            this.TimePrecision = timePrecision;
            this.ExpirationStart = DateTime.Now;

            this.NotifyObjectExpired += s_ExpiredUnregisterNow;
            this.MonitorExpiration();
        }

        #endregion
    }
}

THE ISUBJECT INTERFACE

The ISubject interface merely defines the contract when creating Subject objects. Because the Subject class implements the ISubject interface, then it’s obligated to include the ISubject’s properties and methods. These tenets keeps all Subject objects consistent.


using System;
using System.Collections.Generic;


namespace ObserverClient.Interface
{
    /// 
    /// This is the Subject Interface
    /// 
    public interface ISubject
    {
        #region Interface Operations

        object SubscriptionId { get; set; }
        object CacheId { get; set; }
        object CacheData { get; set; }
        int ExpirationValue { get; set; }
        Enums.enumTimePrecision TimePrecision { get; set; }
        DateTime ExpirationStart { get; set; }
        Enums.enumExpirationType ExpirationType { get; set; }
        void Register();
        void Unregister();

        #endregion
    }
}

THE CLIENT PROGRAM SOURCE CODE

It’s the responsibility of the Client to call the register, unregister, and change methods on the Subjects objects, whenever applicable. The client can also control the lifetime of the Subject object it invokes by overriding the default properties that are set in the Subject’s default constructor. A developer can do this by either injecting the overridden property values in the Subject’s overloaded constructor, or it can accomplish this by simply typing in new lifetime property values on the Subject object and then call the Subject object’s Change() method.

There’s one final note here, and that is that the callback methods are defined by the client program. You’ll see evidence of this where I’ve provided these lines in the source code, below: subject1.NotifyChanged += “Your defined method here!”. This makes it completely flexible, because multiple Subject objects can either share the same notification callback method in the client program, or each instance can define its own.

Also, because the Subject object is generic, I don’t need to implement concrete Subject objects, and they can be defined on-the-fly. This means that I don’t need to redeploy the Observer assembly each time I add a new Subject. This eliminates the other negative that’s typically associated with the Observable design pattern.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Collections;


namespace ObserverClient
{
    class Program
    {
        /// 
        /// The main entry point into the application
        /// 
        static void Main(string[] args)
        {
            // Register subject 1
            Subject subject1 = new Subject { SubscriptionId = "1", CacheId = "1", CacheData = "1" };
            subject1.NotifyChanged += s_testCacheObserver1NotifyChanged_One;
            // Tie the following event handler to any notifications received on this particular subject
            subject1.ExpirationType = Enums.enumExpirationType.Sliding;
            subject1.Register();

            // Register subject 2
            Subject subject2 = new Subject { SubscriptionId = "1", CacheId = "2", CacheData = "2" };
            // Tie the following event handler to any notifications received on this particular subject
            subject2.NotifyChanged += s_testCacheObserver1NotifyChanged_One;
            subject2.Register();

            // Register subject 3
            Subject subject3 = new Subject { SubscriptionId = "1", CacheId = "1", CacheData = "Boom!" };
            // Tie the following event handler to any notifications received on this particular subject
            subject3.NotifyChanged += s_testCacheObserver1NotifyChanged_Two;
            subject3.Change();

            // Unregister subject 2. Only subject 2's notification event should fire and the
            // notification should be specific about the operations taken on it
            subject2.Unregister();

            // Change subject 1's data.  Only subject 2's notification event should fire and the
            // notification should be specific about the operations taken on it
            subject1.CacheData = "Change Me";
            subject1.Change();

            // Hang out and let the system clean up after itself.  Events should only fire for those
            // objects that are self-unregistered.  The system is capable of maintaining itself.
            Console.ReadKey();
        }

        /// 
        /// Notifications are received from the Subject whenever changes have occurred.
        /// 
        static void s_testCacheObserver1NotifyChanged_One(T notifyInfo, Enums.enumSubjectAction action)
        {
            var data = notifyInfo;
        }

        /// 
        /// Notifications are received from the Subject whenever changes have occurred.
        /// 
        static void s_testCacheObserver1NotifyChanged_Two(T notifyInfo, Enums.enumSubjectAction action)
        {
            var data = notifyInfo;
        }
    }
}

THE LIFETIME MANAGER CLASS SOURCE CODE

Again, the LifetimeManager Class is my own creation. The goal of this class, which I’ve already mentioned a couple of times in this article, is to supply default properties that will allow the Subject to maintain its own lifetime without the need for the Unregister() method having to be called explicitly by the client program.

So, while I still believe it’s imperative that the client program explicitly call the Subject object’s Unregister() method, it’s comforting knowing there’s a backup plan in place if for some reason that doesn’t happen.

I’ve also highlighted all of the granular lifetime options in the source code. as you can see for yourself, the code currently accept anything from milliseconds to years, and everything in between. (lightyears would have been really cool) I could have made it even more granular, but I can’t imagine anyone registering and unregistering an observed Subject for less than a millisecond. Also, I can’t image anyone storing observed Subject for as long as a year, even though I’ve created this implementation to observe Subject objects for as long as ±1.7 × 10 to the 308th power years. That seems sufficient, don’t you think? 🙂


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Globalization;

namespace ObserverClient
{
    /// 
    /// The SubjectDecorator class provides additional operations that the Subject class
    /// should be aware of but fall outside its immediate scope of attention.
    /// 
    public class LifetimeManager
    {
        #region Member Variables

        private Timer timer = new Timer();

        #endregion

        #region Properties

        public object SubscriptionId { get; set; }
        public object CacheId { get; set; }
        public object CacheData { get; set; }
        public int ExpirationValue { get; set; }
        public Enums.enumExpirationType ExpirationType { get; set; }
        public Enums.enumTimePrecision TimePrecision { get; set; }
        public DateTime ExpirationStart { get; set; }

        #endregion

        #region Methods

        /// 
        /// Fires when the object's time to live has expired
        /// 
        void s_TimeHasExpired(object sender, ElapsedEventArgs e)
        {
            // Delete the Observer Cache and notify the caller
            NotifyObjectExpired();
        }

        /// 
        /// The delegate for the NotificationChangeEventHandler event
        /// 
        public delegate void NotifyObjectExpiredHandler();

        /// 
        /// Provides expiration monitoring capabilities for itself (self-maintained expiration)
        /// 
        internal void MonitorExpiration()
        {
            double milliseconds = 0;

            switch (this.TimePrecision)
            {
                case Enums.enumTimePrecision.Milliseconds:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddMilliseconds(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                case Enums.enumTimePrecision.Seconds:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddSeconds(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                case Enums.enumTimePrecision.Minutes:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddMinutes(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                case Enums.enumTimePrecision.Hours:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddHours(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                case Enums.enumTimePrecision.Days:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddDays(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                case Enums.enumTimePrecision.Months:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddMonths(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                case Enums.enumTimePrecision.Years:
                    {
                        milliseconds = DateTime.Now.Subtract(DateTime.Now.AddYears(this.ExpirationValue)).TotalMilliseconds;
                        break;
                    }
                default:
                    {
                        break;
                    }

            }

            if(timer.Interval > 0)
            {
                timer.Stop();
                timer.Dispose();
                timer = new Timer(Math.Abs(milliseconds));
                timer.Elapsed += new ElapsedEventHandler(s_TimeHasExpired);
                timer.Enabled = true;
            }
            else
            {
                timer.Elapsed += new ElapsedEventHandler(s_TimeHasExpired);
                timer.Enabled = true;
            }
        }

        #endregion
        
        #region Events

        /// 
        /// Handles the change notification event
        /// 
        public event NotifyObjectExpiredHandler NotifyObjectExpired;

        #endregion        
    }
}

WRAPPING THINGS UP

Well, that’s the Observer design pattern in a nutshell. I’ve even addressed the negatives associated with the design pattern. First, I overcame the “memory leak” issue by creating and tying a configurable LifetimeManager class to the Subject object, which makes sure the Unregister() method always gets called, regardless. Secondly, because I keep the Subject object generic and static, my design only requires one concrete Observer for all Subjects. I’ve also provided you with a Subscription-based model that will allow each Subscriber to observe one or more Subjects in a highly configurable manner. So, I believe that I’ve covered all the bases here…and hopefully then some.

Feel free to stand the example up for yourself. I think I’ve provided you with all the code you need, except for the Enumeration class, which I believe most of you will be able to quickly figure out for yourselves. Anyway, test drive it if you’d like and let me know what you think. I’m particularly interested in what you think about the inclusion of the LifetimeManager class. All comments and questions are always welcome.

Thanks for reading and keep on coding! 🙂

Advertisements

SoftwarePatterns

Author: Cole Francis, Architect

Many of our mentors who leave us with beautiful masterpieces to gaze upon also tend to leave behind prescriptive guidance for us to glean from. But, how we decide to implement their imparted knowledge is always left up to us. What I’ve found is that a fair number of people choose to ignore their contributions altogether or find themselves rushing to judgment when it comes to applying their prescriptive guidance, often proposing the wrong tenants to solve a problem. Both of these approaches often yield poor results…occasionally even catastrophic ones.

The realm of software design and development is very similar in this respect. Designing and developing architectures using proven design patterns, prescribed by industry mentors, almost always lend themselves to a more cohesive understanding of a project’s composite breakdown across the entire project team when applied properly, which results in a solution that’s both easier to build and maintain. The design patterns attempt to achieve this by eliminating most of the technical guesswork involved, as well as overcoming many of the technical complexities by attempting separate the concerns.

So, it’s a little bit shocking to me each time I come across a technical article, or even speak with someone face-to-face, that talks about software design patterns like they’re the plague. Their excuses for not using them vary widely, but some of the more common responses include feeling like they’re boxed in when incorporating them into their designs, or feeling like their projects get into this state of over-design and design paralysis when they’re used, or even experiencing unwanted project bloat due to the added layers of unnecessary abstraction. The bottom line is that these people intentionally dismiss the benefits of the design patterns for…[Insert An Excuse Here].

While some of their concerns are warranted, I maintain that software design patterns are ultimately meant to ease the complexity of the Design and Build Phases, not to increase it. What’s more, if a certain design pattern doesn’t actually provide you with more flexibility and predictability, or if you’re really struggling to make that pattern work for you but it doesn’t quite fit, then my advice to you is that you’re probably doing something wrong. Either you’re trying to apply the wrong pattern to the situation or you don’t fully understand what it is that you’re trying to accomplish.

In addition to this, there’s a huge difference between laying down the initial architecture versus having to maintain that architecture or having to operate inside the architectural structure during the project’s immediate lifecycle and beyond. So, if you aren’t using software design patterns to your advantage, then you’re probably inflicting some degree of punishment not only on your current project team, but also on the individuals who will eventually be responsible for supporting and maintaining your final product.

 

Sure we always joke around about how the final product is someone else’s problem (usually the ASM Team’s), but it’s only a joke because there’s a certain degree of truth to this, and we’ve all been on the receiving end of a poorly designed product at some point in our careers. Trust me when I say that it’s about as much fun and productive as trying to stick your elbow in your eye.

 

I have this theory that if Architects were forced to support their own finished products, particularly longer-term, then they would incorporate patterns into their designs a lot more often than they ordinarily do. It’s been proven many times over that employing design patterns make a solution more predictable, flexible, and in most cases more scalable. These are all very important factors to consider when it comes to a project’s “buildability”, extensibility, maintainability, and even the sporadic refactoring.

Additionally, software development is often considered to be a highly subjective exercise; therefore, taking a little extra time up-front to pair the correct software patterns with right contextual areas of the design will often result in an architecture that’s uniformly framed for your developers, which means that it becomes much more difficult for them to get things wrong during development.

 

I need to point out that this shouldn’t be construed as “boxing people in” or “adding unnecessary bloat to your project”, but instead it should be thought of as incorporating a malleable structure that ubiquitously understood across your entire development team. I would argue that this pervasive level of understanding should make your project much easier to build and maintain over time. It’s really cool and productive when everyone speaks the same language and understands where each unit of functionality lives without having to say it over and over again.

 

Also, keep in mind that design patterns are language agnostic for the most part, so including them early in your Technical Design process shouldn’t necessarily influence the programming language(s) you eventually decide to use during your Build Phase. The benefit is that conversations can be more oriented around the solution’s functionality and less oriented around the specific tools that will eventually be used to accomplish the more detailed technical tasks involved.

Finally…who knows? After many years of practice and hard work, perhaps you will be so inclined as to contribute something back to your own community that’s one day revered as a masterpiece by others…

Thanks for reading and keep on coding! 🙂