Continuing my effort to document my new library, this is the second part of a short series on MfGames Culture CIL, a C# library for globalization written with the intent of supporting non-standard worlds.

Links

  1. Introduction
  2. Language Codes
  3. Country Codes

Singleton

I haven't had much feedback on this library, but the one I did pointed out that I use a "semi" Singleton pattern. Now, singletons have gotten a lot of bad press lately but I don't feel that a blanket statement of "singletons are bad" is the best answer either.

Part of the reason I'm writing this is because System.Globalization is a singleton. There is no way to replace the functionality and I can't easily inject my own codes into the system. The need for that flexibility is one reason I've created this is to get around that limitation.

The biggest different between MfGames Culture CIL and System.Globalization is this:

var manager = CodeManager.Instance;
CodeManager.Instance = new CodeManager();
CodeManager.Instance.Languages = new CustomLanguageCodeManager();

While CodeManager is a concrete type, all of the parameters inside the manager are interfaces. This means that the entire implementation can be replaced without breaking the rest of the system, assuming the interface contract is maintained.

I consider this an acceptable use of the Singleton pattern because it allows you to replace the singleton with a mock- or testing-specific implementation.

Also, working with instance members means that one could use Dependency Injection (DI) to provide the code managers without using the CodeManager class entirely.

public void SomeProcess(ILanguageCodeManager languages)
{}

While others could use this:

public void SomeProcess()
{
    languageCode = CodeManager.Languages.GetIsoAlpha3("eng");
}

So, for those who use DI, they ignore the static instance and inject the code. For those who don't need that flexibility, they can use the static instance. I think this allows for either style of developing without declaring the One True Way™ which I'm not fond of.

Namespaces

All of the namespaces for this example are MfGames.Culture.

using MfGames.Culture;

CodeManager

You may have noticed that I introduced CodeManager while dropping the Instance properties of LanguageCodeManager, CountryCodeManager, and ScriptCodeManager. I had two reasons for this. The first is I was getting a lot more code managers into the system and it was getting overwhelming. The second is Single Responsibility Principle (SRP). CodeManager is purely to handle singleton management of the code managers; this pulls that logic out of the individual managers and keeps their functions pure.

ISO 3166

Like the language codes, I needed country codes for some later functions. I'm also using standard codes, in this case ISO 3166 which defines standard two- and three-character abbreviations for countries. For example, the Republic of Turkey has a two-character code of TR and a three-character one of TUR. Country codes are typically uppercase.

In addition, 3166 defines a numerical code for systems that don't have character-based codes.

Country codes are in the CountryCode class and work much like LanguageCode when it comes to case comparison.

CountryCode turkey1 = new CountryCode("TR", "TUR");
CountryCode turkey2 = new CountryCode("tr", "tur");

Assert.AreEqual(turkey1, turkey2);

The ToString() method prefers two-character codes over three.

CountryCode turkey1 = new CountryCode("TR", "TUR");
CountryCode turkey2 = new CountryCode("tr", "tur");
CountryCode turkey3 = new CountryCode(null, "tur");

Assert.AreEqual("TR", turkey1);
Assert.AreEqual("TR", turkey2);
Assert.AreEqual("TUR", turkey3);

Country Code Manager

The real power of the system comes from CountryCodeManager which provides a single place of access for the known country codes. You can get a CountryCodeManager via CodeManager or directly.

ICountryCodeManager countries1 = CodeManager.Instance.Countries;
ICountryCodeManager countries2 = new CountryCodeManager();

A default set of countries (basically the known list) is embedded into the DLL as a resource. For the CountryCodeManager loaded in the default CodeManager, it is already loaded but for new instances, the AddDefaults() method needs to be called.

ICountryCodeManager countries2 = new CountryCodeManager();

countries2.AddDefaults();

Retrieving codes is pretty simple. The Get method tries all known codes while GetIsoAlpha2 only checks the two-character codes. I did this to allow for a generic system (Get) or something being specific. If a code cannot be found, this returns null.

CountryCode turkey1 = countries.Get("TR");
CountryCode turkey2 = countries.Get("TUR");
CountryCode turkey3 = countries.Get("792");
CountryCode turkey4 = countries.Get(792);
CountryCode turkey5 = countries.GetIsoAlpha2("TR");
CountryCode turkey6 = countries.GetIsoAlpha3("TUR");
CountryCode turkey7 = countries.GetIsoNumeric(792);

What's Next

The following topics are coming up.

2015-03-04