Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Collector, Translator And Formatter Pattern

4.40/5 (7 votes)
3 Feb 2016CPOL6 min read 26.3K   274  
Beginners guide to Collector, Translator And Formatter Pattern

Introduction

In this article, I will be introducing with you the Collector, Translator and Formatter pattern. Before we take a deep dive into the pattern, I am assuming the target audience to have some experience in implementing standard design patterns. Also, have a thorough understanding of Object Oriented Principles and Practices. 

Those of you are interested to learn and implement a mechanism to gather or collect data, apply translator to alter or modify the content and apply formatter to format the data, you are the right candidate to of this article.  As we all know, Design patterns are solution to the commonly re-occurring problems. Today, we are solving the most common practices for collecting, translating and formatting the data. Real world scenario’s 

Let us try to understand with the real world scenario with an example.

  1. Consider an example of a DateTime object. It holds the actual DateTime value but when it comes to formatting of DateTime, say you may be using ToString method of DateTime by specifying the format as a string, IFormatProvider or combination of them to return a specific format of the date. Internally, the ToString method of DateTime class uses an internal class named “DateTimeFormat” to format the DateTime value. Please take a look to http://www.dotnetperls.com/datetime-format to have a feel about the DateTime formatting.
     
  2. An example of a Healthcare Information Exchange (HIE) product, where you wish to transfer or exchange the patient healthcare information in an interoperable manner. Meaning, you wish to export the patient data in a specific format like CCR, CCD, CDA and FHIR etc. Imagine, you have a database stored with all patients, providers and associated patient’s health information. In such scenario, the consumer of the data is not bound with a specific data format but instead accepts the patient health information based on the industry specified format. 

    The software that is responsible for exchanging the health information should be cable of supporting the needs of the client. Here comes the data collector and formatter. The database is a storage medium where your patient data resides. One can collect or gather, translate and transform the data based on client requirement. 

High-level overview of the design pattern

Note: The below formatter types are just for demonstration purpose. 

Image 1

Collector

The “Collector” is an entity which is responsible forgathering the data. Your data can be residing in a flat file, database or any format. In the end, the job of the collector is to gather these data. Defining a clear bounded context is important. The collectors look like a data access components. In some cases, it need not be. So it’s good to separate the collectors from data access components. 

Translator

The “Translator” is an entity which is responsible for modifying or altering the data gathered by a collector.This entity is responsible for massaging the gathered data. There are times you need to perform translation as per the client requirement or apply generic translations. All general and client specific translations rules should go as translators. You don’t necessarily have to go with translators for every collector. Note –The Translators are optional entities. It depends on the domain you are dealing with. 

Formatter

The “Formatter” is an entity which is responsible for formatting the data gathered by the collector or translated data. The “Formatter” converts the data into a specific format based on the client needs. An example of a formatter would be converting the data that is gathered by collector(s) to JSON or XML format.        

Necessity to Decouple the Collector, Translator and Formatter

Let us try to understand the need or necessity to decouple the collector, translator and formatter. We are going to achieve the below things.

Loose coupling and has single responsibility to collect, translate and formatting of data.

  • At times, when you are dealing with the data translation. It’s always a good idea to separate them. Here comes the role of a “Translator”.
  • The formatter don’t have to depend on the collectors. Which means, the collector and the formatter can co-exists and can independently be modified without affecting each other’s. 
  • The lesser the dependency, it is easy to enhance or extend the existing behaviors of a collector, translator or formatter. You have a freedom to extend a formatter to support additional client requirement or needs. 

Background

Knowledge and understanding of standard design patterns.

Using the code

Let us take look into the sample code and see how the pattern can be implemented. For demonstration purposes, I am taking an example of a customized Health Level 7 (HL7) Message from Open Dental. Within the collector, we will be gathering a HL7 message object consisting of a ‘Message’ entity. Further, the ‘Message’ entity consists of ‘MessageHeader’, ‘PatientIdentification’, ‘Guarantor’, ‘Insurance’ entities. 

public class HL7MessageRoot  
{  
    public MessageMessage  
    {  
        get;  
        set;  
    }  
}  
  
public classMessage  
{  
    public MessageHeaderMessageHeader  
    {  
        get;  
        set;  
    }  
    public PatientIdentificationPatientIdentification   
    {  
        get;  
        set;  
    }  
    public GuarantorGuarantor  
    {  
        get;  
        set;  
    }  
    public InsuranceInsurance   
    {  
        get;  
        set;  
    }  
}  

For the demo purpose, we are not going to hit the database and build the HL7 message object. Instead, we are going to return a Mock object. Below is the high level code snippet for the same.

public classMockHL7Message   
{  
    public HL7MessageRoot GetHL7Message()   
    {  
        return new HL7MessageRoot   
        {  
            Message = GetMessage()  
        };  
    }  
  
    MessageGetMessage()  
    {  
            return newMessage   
            {  
                MessageHeader = GetMessageHeader(),  
                    PatientIdentification = GetPatientIdentification(),  
                    Guarantor = GetGuarantor(),  
                    Insurance = GetInsurance()  
            };  
        }  
        .  
        .  
        .  
}  

Here’s the code snippet of our Collector.

public class HL7MessageCollector  
{  
    public HL7MessageRoot Collect()   
    {  
        // Return Mock Data for now  
        return new MockHL7Message().GetHL7Message();  
    }  
}  

Let us now discuss about ‘Translators’. We are going to have two of them. One to format the phone number and the other to mark the SSN number. Below is the code snippet for the same.

public class PhoneNumberTranslator: ITranslate  
{  
        public string Translate(stringphoneNumber)  
        {  
            return phoneNumber.ToString()  
                .Replace("-", "")  
                .Replace("(", "")  
                .Replace(")", "");  
        }  
}  

//Reused code - http://stackoverflow.com/questions/5254197/format-ssn-using-regex  
public class SSNMaskTranslator: ITranslate   
{  
    publicstring Translate(stringoriginalSSN)   
    {  
        string ssn = originalSSN.ToString();  
        if (ssn.Length < 5) originalSSN = ssn;  
        var trailingNumbers = ssn.Substring(ssn.Length - 4);  
        var leadingNumbers = ssn.Substring(0, ssn.Length - 4);  
        var maskedLeadingNumbers = Regex.Replace(leadingNumbers, @ "[0-9]", "X");  
        return maskedLeadingNumbers + trailingNumbers;  
    }  
}  

Now the last important part our solution is the formatter. As part of the demo sample, we will be having two kinds of formatters. One to format the HL7 Message to XML and the other to JSON. Below is the code snippet of HL7JsonFormatter, we are making use of JSON.NET to serialize the HL7 message object. For displaying the JSON output on console, I am using an optional parameter Formatting.Indented.

public class HL7JsonFormatter: IHL7Formatter  
{  
    public string FormatHL7Message(HL7MessageRoot hL7MessageRoot)  
    {  
        return JsonConvert.SerializeObject(hL7MessageRoot, Formatting.Indented);  
    }  
}  

Below is the code snippet of HL7XMLFormatter. The “FormatHL7Message” method takes a HL7 Message root object and is responsible for building an XML based HL7 message by creating an XML document and adding XML elements for ‘MessageHeader’, ‘PatientIdentification’,’Gaurantor’ and ‘Insurance’. 

public class HL7XMLFormatter: IHL7Formatter   
{  
    public string FormatHL7Message(HL7MessageRoot hL7MessageRoot)  
    {  
        XDocumentxDocument = new XDocument();  
        var messageElement = new XElement("Message");  
  
        messageElement.Add(GetMessageHeader(hL7MessageRoot));  
        messageElement.Add(GetPatientIdentification(hL7MessageRoot));  
        messageElement.Add(GetGuarantor(hL7MessageRoot));  
        messageElement.Add(GetInsurance(hL7MessageRoot));  
        xDocument.Add(messageElement);  
  
        StringBuilderstringBuilder = new StringBuilder();  
        using(TextWriter writer = new StringWriter(stringBuilder))  
        {  
            xDocument.Save(writer);  
        }  
  
        returnstringBuilder.ToString();  
    }  
  
    XElementGetMessageHeader(HL7MessageRoot hL7MessageRoot)  
    {  
        return new XElement("MessageHeader",  
            new XElement("DateTimeOfMessage",  
                hL7MessageRoot.Message.MessageHeader.DateTimeOfMessage),  
            new XElement("MessageType",  
                hL7MessageRoot.Message.MessageHeader.MessageType),  
            new XElement("OpenDentalVersion",  
                hL7MessageRoot.Message.MessageHeader.OpenDentalVersion));  
    }  
  
    XElementGetPatientIdentification(HL7MessageRoot hL7MessageRoot)  
    {  
        return new XElement("PatientIdentification",  
            newXElement("NameLast",  
                hL7MessageRoot.Message.PatientIdentification.NameLast),  
            newXElement("NameFirst",  
                hL7MessageRoot.Message.PatientIdentification.NameFirst),  
            newXElement("NameMiddle",  
                hL7MessageRoot.Message.PatientIdentification.NameMiddle),  
            newXElement("DateOfBirth",  
                hL7MessageRoot.Message.PatientIdentification.DateOfBirth),  
            newXElement("Sex",  
                hL7MessageRoot.Message.PatientIdentification.Sex),  
            newXElement("AliasFirst",  
                hL7MessageRoot.Message.PatientIdentification.AliasFirst),  
            newXElement("AddressStreet",  
                hL7MessageRoot.Message.PatientIdentification.AddressStreet),  
            newXElement("AddressOtherDesignation",  
                hL7MessageRoot.Message.PatientIdentification.AddressOtherDesignation),  
            newXElement("AddressCity",  
                hL7MessageRoot.Message.PatientIdentification.AddressCity),  
            newXElement("AddressStateOrProvince",  
                hL7MessageRoot.Message.PatientIdentification.AddressStateOrProvince),  
            newXElement("AddressZipOrPostalCode",  
                hL7MessageRoot.Message.PatientIdentification.AddressZipOrPostalCode),  
            newXElement("PhoneHome",  
                hL7MessageRoot.Message.PatientIdentification.PhoneHome),  
            newXElement("EmailAddressHome",  
                hL7MessageRoot.Message.PatientIdentification.EmailAddressHome),  
            newXElement("PhoneBusiness",  
                hL7MessageRoot.Message.PatientIdentification.PhoneBusiness),  
            newXElement("MaritalStatus",  
                hL7MessageRoot.Message.PatientIdentification.MaritalStatus),  
            newXElement("SSN",  
                hL7MessageRoot.Message.PatientIdentification.SSN),  
            newXElement("NotePhoneAddress",  
                hL7MessageRoot.Message.PatientIdentification.NotePhoneAddress),  
            newXElement("NoteMedicalComplete", 
                hL7MessageRoot.Message.PatientIdentification.NoteMedicalComplete));  
    }  
  
    XElementGetGuarantor(HL7MessageRoot hL7MessageRoot)  
    {  
        returnnewXElement("Guarantor",  
            newXElement("NameLast",  
                hL7MessageRoot.Message.Guarantor.NameLast),  
            newXElement("NameFirst",  
                hL7MessageRoot.Message.Guarantor.NameFirst),  
            newXElement("NameMiddle",  
                hL7MessageRoot.Message.Guarantor.NameMiddle),  
            newXElement("AddressStreet",  
                hL7MessageRoot.Message.Guarantor.AddressStreet),  
            newXElement("AddressOtherDesignation",  
                hL7MessageRoot.Message.Guarantor.AddressOtherDesignation),  
            newXElement("AddressCity",  
                hL7MessageRoot.Message.Guarantor.AddressCity),  
            newXElement("AddressStateOrProvince",  
                hL7MessageRoot.Message.Guarantor.AddressStateOrProvince),  
            newXElement("AddressZipOrPostalCode",  
                hL7MessageRoot.Message.Guarantor.AddressZipOrPostalCode),  
            newXElement("PhoneHome",  
                hL7MessageRoot.Message.Guarantor.PhoneHome),  
            newXElement("EmailAddressHome",  
                hL7MessageRoot.Message.Guarantor.EmailAddressHome),  
            newXElement("PhoneBusiness",  
                hL7MessageRoot.Message.Guarantor.PhoneBusiness),  
            newXElement("DateOfBirth",  
                hL7MessageRoot.Message.Guarantor.DateOfBirth),  
            newXElement("Sex",  
                hL7MessageRoot.Message.Guarantor.Sex),  
            newXElement("GuarantorRelationship",  
                hL7MessageRoot.Message.Guarantor.GuarantorRelationship),  
            newXElement("SSN",  
                hL7MessageRoot.Message.Guarantor.SSN),  
            newXElement("EmployerName",  
                hL7MessageRoot.Message.Guarantor.EmployerName),  
            newXElement("MaritalStatus",  
                hL7MessageRoot.Message.Guarantor.MaritalStatus));  
    }  
  
    XElementGetInsurance(HL7MessageRoot hL7MessageRoot)  
    {  
        returnnewXElement("Insurance",  
            newXElement("CompanyName",  
                hL7MessageRoot.Message.Insurance.CompanyName),  
            newXElement("AddressStreet",  
                hL7MessageRoot.Message.Insurance.AddressStreet),  
            newXElement("AddressOtherDesignation",  
                hL7MessageRoot.Message.Insurance.AddressOtherDesignation),  
            newXElement("AddressCity",  
                hL7MessageRoot.Message.Insurance.AddressCity),  
            newXElement("AddressStateOrProvince",  
                hL7MessageRoot.Message.Insurance.AddressStateOrProvince),  
            newXElement("AddressZipOrPostalCode",  
                hL7MessageRoot.Message.Insurance.AddressZipOrPostalCode),  
            newXElement("PhoneNumber",  
                hL7MessageRoot.Message.Insurance.PhoneNumber),  
            newXElement("GroupNumber",  
                hL7MessageRoot.Message.Insurance.GroupNumber),  
            newXElement("GroupName",  
                hL7MessageRoot.Message.Insurance.GroupName),  
            newXElement("InsuredGroupEmpName",  
                hL7MessageRoot.Message.Insurance.InsuredGroupEmpName),  
            newXElement("PlanEffectiveDate",  
                hL7MessageRoot.Message.Insurance.PlanEffectiveDate),  
            newXElement("PlanExpirationDate",  
                hL7MessageRoot.Message.Insurance.PlanExpirationDate),  
            newXElement("InsuredsNameLast",  
                hL7MessageRoot.Message.Insurance.InsuredsNameLast),  
            newXElement("InsuredsNameFirst",  
                hL7MessageRoot.Message.Insurance.InsuredsNameFirst),  
            newXElement("InsuredsNameMiddle",  
                hL7MessageRoot.Message.Insurance.InsuredsNameMiddle),  
            newXElement("InsuredsRelationToPat",  
                hL7MessageRoot.Message.Insurance.InsuredsRelationToPat),  
            newXElement("InsuredsDateOfBirth",  
                hL7MessageRoot.Message.Insurance.InsuredsDateOfBirth),  
            newXElement("InsuredsAddressStreet",  
                hL7MessageRoot.Message.Insurance.InsuredsAddressStreet),  
            newXElement("InsuredsAddressOtherDesignation",  
                hL7MessageRoot.Message.Insurance.InsuredsAddressOtherDesignation),  
            newXElement("InsuredsAddressCity",  
                hL7MessageRoot.Message.Insurance.InsuredsAddressCity),  
            newXElement("InsuredsAddressStateOrProvince",  
                hL7MessageRoot.Message.Insurance.InsuredsAddressStateOrProvince),  
            newXElement("InsuredsAddressZipOrPostalCode",  
                hL7MessageRoot.Message.Insurance.InsuredsAddressZipOrPostalCode),  
            newXElement("AssignmentOfBenefits",  
                hL7MessageRoot.Message.Insurance.AssignmentOfBenefits),  
            newXElement("ReleaseInformationCode",  
                hL7MessageRoot.Message.Insurance.ReleaseInformationCode),  
            newXElement("PolicyNumber",  
                hL7MessageRoot.Message.Insurance.PolicyNumber),  
            newXElement("PolicyDeductible",  
                hL7MessageRoot.Message.Insurance.PolicyDeductible),  
            newXElement("PolicyLimitAmount",  
                hL7MessageRoot.Message.Insurance.PolicyLimitAmount),  
            newXElement("InsuredsSex",  
                hL7MessageRoot.Message.Insurance.InsuredsSex),  
            newXElement("InsuredsSSN",  
                hL7MessageRoot.Message.Insurance.InsuredsSSN),  
            newXElement("InsuredsPhoneHome",  
                hL7MessageRoot.Message.Insurance.InsuredsPhoneHome),  
            newXElement("NotePlan",  
                hL7MessageRoot.Message.Insurance.NotePlan));  
    }  
}  

Here’s the code snippet of an HL7MessageFormatter, takes the HL7MessageRoot object and formats the as HL7 message per the specified formatter. 

public class HL7MessageFormatter   
{  
    HL7MessageRoot hL7MessageRootObject;  
  
    public HL7MessageFormatter(HL7MessageRoot hL7MessageRoot)  
    {  
        hL7MessageRootObject = hL7MessageRoot;  
    }  
  
    public string Format(IHL7Formatter formatter)  
    {  
        return formatter.FormatHL7Message(hL7MessageRootObject);  
    }  
}  

Let us take a look into the main code and see how we can wire up the collector, translator and formatter. Below is the code snippet for the same. First we gather or collect the data and then we are going to translate the phone number, SSN masking etc. At last, we make a call to format the HL7 message object to XML and JSON by using the formatters that we built. 

static void Main(string[] args)  
{  
    // Collect or Gather Data  
    var hL7MessageCollector = newHL7MessageCollector();  
    var hL7MessageRoot = hL7MessageCollector.Collect();  
    
    // Translate data  
    Translate(hL7MessageRoot);  
  
    // Format and display as XML  
    Console.WriteLine("Display HL7 message as XML\n");  
    varxmlOutput = FormatToXML(hL7MessageRoot);  
    Console.WriteLine(xmlOutput);  
  
    // Format and display as JSON  
    Console.WriteLine("\nDisplay HL7 message as JSON\n");  
    varjsonOutput = FormatToJSON(hL7MessageRoot);  
    Console.WriteLine(jsonOutput);  
  
    Console.ReadLine();  
}  

Here’s the code snippet for formatting the HL7 message object.

static stringFormatToXML(HL7MessageRoot hL7MessageRoot)  
{  
    var hL7MessageFormatter = newHL7MessageFormatter(hL7MessageRoot);  
    return hL7MessageFormatter.Format(newHL7XMLFormatter());  
}  
  
static stringFormatToJSON(HL7MessageRoot hL7MessageRoot)  
{  
    var hL7JSONFormatter = newHL7MessageFormatter(hL7MessageRoot);  
    return hL7JSONFormatter.Format(newHL7JsonFormatter());  
}  

Below is the code snippet of “Translator”, where we are making use of PhoneNumberTranslator to translate or alter the phone number by removing hyphen’s and parenthesis. SSNMaskTranslator to mask the SSN number. 

static void Translate(HL7MessageRoot hL7MessageRoot)  
{  
    var phoneNumber = hL7MessageRoot.Message.PatientIdentification.PhoneHome;  
    var phoneNumberTranslator = newPhoneNumberTranslator();  
  
    if (!string.IsNullOrEmpty(phoneNumber))   
    {  
        hL7MessageRoot.Message.PatientIdentification.PhoneHome = 
               phoneNumberTranslator.Translate(phoneNumber);  
    }  
  
    var ssn = hL7MessageRoot.Message.PatientIdentification.SSN;  
    var ssnMaskTranslator = newSSNMaskTranslator();  
  
    if (!string.IsNullOrEmpty(ssn))  
    {  
        hL7MessageRoot.Message.PatientIdentification.SSN = ssnMaskTranslator.Translate(ssn);  
    }  
  
    ssn = hL7MessageRoot.Message.Guarantor.SSN;  
    if (!string.IsNullOrEmpty(ssn))  
    {  
        hL7MessageRoot.Message.Guarantor.SSN = ssnMaskTranslator.Translate(ssn);  
    }  
}  

HL7 Model Class Diagram

Image 2

Translator Class Diagram

Below is the class diagram of Translator classes.

Note: The below classes are part of the sample application, the translator is optional and purely depends on the domain you are working.

Image 3

Fromatter Class Diagram

Image 4

HL7 Message as XML

Here’s the screenshot of how the HL7 message gets formatted to XML. 

Image 5

HL7 Message as JSON

Here’s the screenshot of how the HL7 message gets formatted to JSON.

Image 6

Points of Interest

Although it's a baby step in attempting to create a design pattern for solving a specific type of problem, I am happy to share something I had in my mind from a long time. I really feel excited :)

Open and constructive critisism are welcome!

The latest upto date code can be also found at - https://github.com/ranjancse26/CollectorFormatterSample

History

Version 1.0 - Initial draft published to CP on 02/03/2015.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)