15 min read

In this article by Praseed Pai, and Shine Xavier, authors of the book .NET Design Patterns, we will try to understand the necessity of choosing a pattern-based approach to software development. We start with some principles of software development, which one might find useful while undertaking large projects. The working example in the article starts with a requirements specification and progresses towards a preliminary implementation. We will then try to iteratively improve the solution using patterns and idioms, and come up with a good design that supports a well-defined programming Interface. In this process, we will learn about some software development principles (listed below) one can adhere to, including the following:

  • SOLID principles for OOP
  • Three key uses of design patterns
  • Arlow/Nuestadt archetype patterns
  • Entity, value, and data transfer objects
  • Leveraging the .NET Reflection API for plug-in architecture

(For more resources related to this topic, see here.)

Some principles of software development

Writing quality production code consistently is not easy without some foundational principles under your belt. The purpose of this section is to whet the developer’s appetite, and towards the end, some references are given for detailed study. Detailed coverage of these principles warrants a separate book on its own scale. The authors have tried to assimilate the following key principles of software development which would help one write quality code:

  • KISS: Keep it simple, Stupid
  • DRY: Don’t repeat yourself
  • YAGNI: You aren’t gonna need it
  • Low coupling: Minimize coupling between classes
  • SOLID principles: Principles for better OOP

William of Ockham had framed the maxim Keep it simple, Stupid (KISS). It is also called law of parsimony. In programming terms, it can be translated as “writing code in a straightforward manner, focusing on a particular solution that solves the problem at hand”.

This maxim is important because, most often, developers fall into the trap of writing code in a generic manner for unwarranted extensibility. Even though it initially looks attractive, things slowly go out of bounds. The accidental complexity introduced in the code base for catering to improbable scenarios, often reduces readability and maintainability. The KISS principle can be applied to every human endeavor. Learn more about KISS principle by consulting the Web.

Don’t repeat yourself (DRY), a maxim which most programmers often forget while implementing their domain logic. Most often, in a collaborative development scenario, code gets duplicated inadvertently due to lack of communication and proper design specifications.

This bloats the code base, induces subtle bugs, and make things really difficult to change. By following the DRY maxim at all stages of development, we can avoid additional effort and make the code consistent. The opposite of DRY is write everything twice (WET).

You aren’t gonna need it (YAGNI), a principle that compliments the KISS axiom. It serves as a warning for people who try to write code in the most general manner, anticipating changes right from the word go,.

Too often, in practice, most of this code is not used to make potential code smells.

While writing code, one should try to make sure that there are no hard-coded references to concrete classes. It is advisable to program to an interface as opposed to an implementation.

This is a key principle which many patterns use to provide behavior acquisition at runtime. A dependency injection framework could be used to reduce coupling between classes.

SOLID principles are a set of guidelines for writing better object-oriented software. It is a mnemonic acronym that embodies the following five principles:

1

Single Responsibility Principle (SRP)

A class should have only one responsibility. If it is doing more than one unrelated thing, we need to split the class.

2

Open Close Principle (OCP)

A class should be open for extension, closed for modification.

3

Liskov Substitution Principle (LSP)

Named after Barbara Liskov, a Turing Award laureate, who postulated that a sub-class (derived class) could substitute any super class (base class) references without affecting the functionality. Even though it looks like stating the obvious, most implementations have quirks which violate this principle.

4

Interface segregation principle (ISP)

It is more desirable to have multiple interfaces for a class (such classes can also be called components) than having one Uber interface that forces implementation of all methods (both relevant and non-relevant to the solution context).

5

Dependency Inversion (DI)

This is a principle which is very useful for Framework design. In the case of Frameworks, the client code will be invoked by server code, as opposed to the usual process of client invoking the server. The main principle here is that abstraction should not depend upon details, rather, details should depend upon abstraction. This is also called the “Hollywood Principle” (Do not call us, we will call you back).

The authors consider the preceding five principles primarily as a verification mechanism. This will be demonstrated by verifying the ensuing case study implementations for violation of these principles.

Karl Seguin has written an e-book titled Foundations of Programming – Building Better Software, which covers most of what has been outlined here. Read his book to gain an in-depth understanding of most of these topics. The SOLID principles are well covered in the Wikipedia page on the subject, which can be retrieved from https://en.wikipedia.org/wiki/SOLID_(object-oriented_design). Robert Martin’s Agile Principles, Patterns, and Practices in C# is a definitive book on learning about SOLID, as Robert Martin itself is the creator of these principles, even though Michael Feathers coined the acronym.

Why patterns are required?

According to the authors, the three key advantages of pattern-oriented software development that stand out are as follows:

  • A language/platform-agnostic way to communicate about software artifacts
  • A tool for refactoring initiatives (targets for refactoring)
  • Better API design

With the advent of the pattern movement, the software development community got a canonical language to communicate about software design, architecture, and implementation. Software development is a craft which has got trade-offs attached to each strategy, and there are multiple ways to develop software. The various pattern catalogues brought some conceptual unification for this cacophony in software development.

Most developers around the world today who are worth their salt, can understand and speak this language. We believe you will be able to do the same at the end of the article. Fancy yourself stating the following about your recent implementation:

For our tax computation example, we have used command pattern to handle the computation logic. The commands (handlers) are configured using an XML file, and a factory method takes care of the instantiation of classes on the fly using Lazy loading. We cache the commands, and avoid instantiation of more objects by imposing singleton constraints on the invocation. We support prototype pattern where command objects can be cloned. The command objects have a base implementation, where concrete command objects use the template method pattern to override methods which are necessary. The command objects are implemented using the design by contracts idiom. The whole mechanism is encapsulated using a Façade class, which acts as an API layer for the application logic. The application logic uses entity objects (reference) to store the taxable entities, attributes like tax parameters are stored as value objects. We use data transfer object (DTO) to transfer the data from the application layer to the computational layer. Arlow/Nuestadt-based archetype pattern is the unit of structuring the tax computation logic.

For some developers, the preceding language/platform-independent description of the software being developed is enough to understand the approach taken. This will boos developer productivity (during all phases of SDLC, including development, maintenance, and support) as the developers will be able to get a good mental model of the code base. Without Pattern catalogs, such succinct descriptions of the design or implementation would have been impossible.

In an Agile software development scenario, we develop software in an iterative fashion. Once we reach a certain maturity in a module, developers refactor their code. While refactoring a module, patterns do help in organizing the logic. The case study given next will help you to understand the rationale behind “Patterns as refactoring targets”.

APIs based on well-defined patterns are easy to use and impose less cognitive load on programmers. The success of the ASP.NET MVC framework, NHibernate, and API’s for writing HTTP modules and handlers in the ASP.NET pipeline are a few testimonies to the process.

Personal income tax computation – A case study

Rather than explaining the advantages of patterns, the following example will help us to see things in action. Computation of the annual income tax is a well-known problem domain across the globe. We have chosen an application domain which is well known to focus on the software development issues.

The application should receive inputs regarding the demographic profile (UID, Name, Age, Sex, Location) of a citizen and the income details (Basic, DA, HRA, CESS, Deductions) to compute his tax liability.

The System should have discriminants based on the demographic profile, and have a separate logic for senior citizens, juveniles, disabled people, old females, and others. By discriminant we mean demographic that parameters like age, sex and location should determine the category to which a person belongs and apply category-specific computation for that individual. As a first iteration, we will implement logic for the senior citizen and ordinary citizen category.

After preliminary discussion, our developer created a prototype screen as shown in the following image:

Archetypes and business archetype pattern

The legendary Swiss psychologist, Carl Gustav Jung, created the concept of archetypes to explain fundamental entities which arise from a common repository of human experiences. The concept of archetypes percolated to the software industry from psychology. The Arlow/Nuestadt patterns describe business archetype patterns like Party, Customer Call, Product, Money, Unit, Inventory, and so on. An Example is the Apache Maven archetype, which helps us to generate projects of different natures like J2EE apps, Eclipse plugins, OSGI projects, and so on. The Microsoft patterns and practices describes archetypes for targeting builds like Web applications, rich client application, mobile applications, and services applications. Various domain-specific archetypes can exist in respective contexts as organizing and structuring mechanisms.

In our case, we will define some archetypes which are common in the taxation domain. Some of the key archetypes in this domain are:

Sr.no

Archetype

Description

1

SeniorCitizenFemale

Tax payers who are female, and above the age of 60 years

2

SeniorCitizen

Tax payers who are male, and above the age of 60 years

3

OrdinaryCitizen

Tax payers who are Male/Female, and above 18 years of age

3

DisabledCitizen

Tax payers who have any disability

4

MilitaryPersonnel

Tax payers who are military personnel

5

Juveniles

Tax payers whose age is less than 18 years

We will use demographic parameters as discriminant to find the archetype which corresponds to the entity. The whole idea of inducing archetypes is to organize the tax computation logic around them. Once we are able to resolve the archetypes, it is easy to locate and delegate the computations corresponding to the archetypes.

Entity, value, and data transfer objects

We are going to create a class which represents a citizen. Since citizen needs to be uniquely identified, we are going to create an entity object, which is also called reference object (from DDD catalog). The universal identifier (UID) of an entity object is the handle which an application refers. Entity objects are not identified by their attributes, as there can be two people with the same name. The ID uniquely identifies an entity object. The definition of an entity object is given as follows:

public class TaxableEntity
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
  public char Sex { get; set; }
  public string Location { get; set; }
  public TaxParamVO taxparams { get; set; }
}

In the preceding class definition, Id uniquely identifies the entity object. TaxParams is a value object (from DDD catalog) associated with the entity object. Value objects do not have a conceptual identity. They describe some attributes of things (entities). The definition of TaxParams is given as follows:

public class TaxParamVO
{
  public double Basic {get;set;}
  public double DA { get; set; }
  public double HRA { get; set; }
  public double Allowance { get; set; }
  public double Deductions { get; set; }
  public double Cess { get; set; }
  public double TaxLiability { get; set; }
  public bool Computed { get; set; }
 }

While writing applications ever since Smalltalk, Model-view-controller (MVC) is the most dominant paradigm for structuring applications. The application is split into a model layer ( which mostly deals with data), view layer (which acts as a display layer), and a controller (to mediate between the two). In the Web development scenario, they are physically partitioned across machines. To transfer data between layers, the J2EE pattern catalog identified the DTO to transfer data between layers. The DTO object is defined as follows:

public class TaxDTO
{
  public int id { }
  public TaxParamVO taxparams { }
}

If the layering exists within the same process, we can transfer these objects as-is. If layers are partitioned across processes or systems, we can use XML or JSON serialization to transfer objects between the layers.

A computation engine

We need to separate UI processing, input validation, and computation to create a solution which can be extended to handle additional requirements. The computation engine will execute different logic depending upon the command received. The GoF command pattern is leveraged for executing the logic based on the command received.

The command pattern consists of four constituents. They are:

  • Command object
  • Parameters
  • Command Dispatcher
  • Client

The command object’s interface has an Execute method. The parameters to the command objects are passed through a bag. The client invokes the command object by passing the parameters through a bag to be consumed by the Command Dispatcher. The Parameters are passed to the command object through the following data structure:

public class COMPUTATION_CONTEXT
{
  private Dictionary<String, Object> symbols = new
  Dictionary<String, Object>();
  public void Put(string k, Object value) {
    symbols.Add(k, value);
  }
  public Object Get(string k) { return symbols[k]; }
}

The ComputationCommand interface, which all the command objects implement, has only one Execute method, which is shown next. The Execute method takes a bag as parameter. The COMPUTATION_CONTEXT data structure acts as the bag here.

Interface ComputationCommand
{
  bool Execute(COMPUTATION_CONTEXT ctx);

}

Since we have already implemented a command interface and bag to transfer the parameters, it is time that we implement a command object. For the sake of simplicity, we will implement two commands where we hardcode the tax liability.

public class SeniorCitizenCommand : ComputationCommand
{
  public bool Execute(COMPUTATION_CONTEXT ctx)
  {
    TaxDTO td = (TaxDTO)ctx.Get("tax_cargo");
    //---- Instead of computation, we are assigning
    //---- constant tax for each arcetypes
    td.taxparams.TaxLiability = 1000;
    td.taxparams.Computed = true;
    return true;
  }
}

public class OrdinaryCitizenCommand : ComputationCommand
{
  public bool Execute(COMPUTATION_CONTEXT ctx)
  {
    TaxDTO td = (TaxDTO)ctx.Get("tax_cargo");
    //---- Instead of computation, we are assigning
    //---- constant tax for each arcetypes
    td.taxparams.TaxLiability = 1500;
    td.taxparams.Computed = true;
    return true;
  }
}

The commands will be invoked by a CommandDispatcher Object, which takes an archetype string and a COMPUTATION_CONTEXT object. The CommandDispatcher acts as an API layer for the application.

class CommandDispatcher
{
  public static bool Dispatch(string archetype,   COMPUTATION_CONTEXT ctx)
  {
    if (archetype == "SeniorCitizen")
    {
      SeniorCitizenCommand cmd = new SeniorCitizenCommand();
      return cmd.Execute(ctx);
    }
    else if (archetype == "OrdinaryCitizen")
    {
       OrdinaryCitizenCommand cmd = new OrdinaryCitizenCommand();
       return cmd.Execute(ctx);
    }
    else { 
      return false;
    }
  }
}

The application to engine communication

The data from the application UI, be it Web or Desktop, has to flow to the computation engine. The following ViewHandler routine shows how data, retrieved from the application UI, is passed to the engine, via the Command Dispatcher, by a client:

public static void ViewHandler(TaxCalcForm tf)
{
  TaxableEntity te = GetEntityFromUI(tf);
  if (te == null){
    ShowError();
    return;
  }
  string archetype = ComputeArchetype(te);
  COMPUTATION_CONTEXT ctx = new COMPUTATION_CONTEXT();
  TaxDTO td = new TaxDTO { id = te.id, taxparams =   te.taxparams};
  ctx.Put("tax_cargo",td);
  bool rs = CommandDispatcher.Dispatch(archetype, ctx);
  if ( rs ) {
    TaxDTO temp = (TaxDTO)ctx.Get("tax_cargo");
    tf.Liabilitytxt.Text =     Convert.ToString(temp.taxparams.TaxLiability);
    tf.Refresh();
  }
}

At this point, imagine that a change in requirement has been received from the stakeholders. Now, we need to support tax computation for new categories.

Initially, we had different computations for senior citizen and ordinary citizen. Now we need to add new Archetypes. At the same time, to make the software extensible (loosely coupled) and maintainable, it would be ideal if we provide the capability to support new Archetypes in a configurable manner as opposed to recompiling the application for every new archetype owing to concrete references.

The Command Dispatcher object does not scale well to handle additional archetypes. We need to change the assembly whenever a new archetype is included, as the tax computation logic varies for each archetype. We need to create a pluggable architecture to add or remove archetypes at will.

The plugin system to make system extensible

Writing system logic without impacting the application warrants a mechanism—that of loading a class on the fly. Luckily, the .NET Reflection API provides a mechanism for one to load a class during runtime, and invoke methods within it. A developer worth his salt should learn the Reflection API to write systems which change dynamically. In fact, most of the technologies like ASP.NET, Entity framework, .NET Remoting, and WCF work because of the availability of Reflection API in the .NET stack.

Henceforth, we will be using an XML configuration file to specify our tax computation logic. A sample XML file is given next:

<?xml version="1.0"?>
<plugins>
  <plugin archetype ="OrindaryCitizen"   command="TaxEngine.OrdinaryCitizenCommand"/>
  <plugin archetype="SeniorCitizen"   command="TaxEngine.SeniorCitizenCommand"/>
</plugins>

The contents of the XML file can be read very easily using LINQ to XML. We will be generating a Dictionary object by the following code snippet:

private Dictionary<string,string> LoadData(string xmlfile)
{
  return XDocument.Load(xmlfile)
  .Descendants("plugins")
  .Descendants("plugin")
  .ToDictionary(p => p.Attribute("archetype").Value,
  p => p.Attribute("command").Value);
}

Summary

In this article, we have covered quite a lot of ground in understanding why pattern-oriented software development is a good way to develop modern software. We started the article citing some key principles. We progressed further to demonstrate the applicability of these key principles by iteratively skinning an application which is extensible and resilient to changes.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here