2

I'm looking for some pointers on class design for a global application.

Let's say I have to make a class structure to manage products, and the products are sold in different countries. Some of the fields for the product will have the same value across all countries (eg. product code, ERP Description) I will call these "international" fields, and some fields will be specific to a single country (eg. Local Description), lets call these "local" fields. Of course, some "local" fields will be the same for groups of countries (es. weight : 1 kilogram / 2 pounds). Also I expect that not all countries will have values for all fields.

Which fields are "international" and which fields are "local" may change from one installation to another and I am reluctant to bake this into the design as I'm sure it will bite me later on.

So, I'm trying to figure out how to structure the objects so that I can use a product at an international level and always refer to the same "product", but also maintain and use the local information when necessary?

Just to be clear, I'm not talking about user-locale, number or date formatting etc. The source data is coming from different database schemas (one for each country). The end product will be written in C#.

I'm wondering if anyone has experience or can point me to a pattern that would provide a good solution to this before I go and reinvent the wheel?

6
  • 1
    Is DB schema already defined or can you change it? Any more info about this schema? Because IMO the answer will result a lot on how this schema looks.
    – Euphoric
    Aug 2, 2012 at 9:54
  • Please clarify how different it is for you to distinguish between international fields and other fields exactly?
    – NoChance
    Aug 2, 2012 at 10:04
  • the "collected" DB Schema is not currently defined, I can define it as I wish. the source data schemas are defined.
    – Nick B.
    Aug 2, 2012 at 12:42
  • @Emmad I'm not really sure I understand your question. I think that some fields will be clearly international, and some will change based the data the customer data provides. My experience is that no two customers treat their multinational data in the same way. I can apply the YAGNI principle, make my best guess and backtrack later but I've made that mistake before and would like to ensure that the application is adaptable to diffent companies thoughts on what is international and what is defined at a local company level.
    – Nick B.
    Aug 2, 2012 at 12:45
  • @NickB. For a product class, I can't really see a distinction between local and international "properties". I understand that a TV sold in USA may run 120 Volts and when sold in Kuwait may run on 220 Volts only but then we are talking about 2 different product numbers. Also I see that measurements may use different units but that does not mean you store the values with different measures in the database. In such a case, it is best if you store them using a standard measure system and have a conversion based on the country. I raise this point so that you don't over complicate your app.
    – NoChance
    Aug 2, 2012 at 13:20

4 Answers 4

1

I assume you have a database of some sort. I'd create a factory class that creates the objects (e.g. Product) from the data in the database.

Let's say your designing the Product class. It has some of your international fields:

public class Product
{
    private string ean;
}

For internationalized things like weights I'd write some structs Weight that allows you to set the unit (pounds, kilograms). Use one consistent unit of weight in your database (for example kilograms) and use that to create the object. Then code your ToString to return the value localized in the specified unit. This is similar to how DateTime can take a date/time in UTC and 'localized' it to the user's timezone.

When there is no weight, use for example a weight of -1 and declare this as static readonly Weight None = new Weight(-1).

public class Product
{
    private string ean;
    private Weight weight;
    private DateTime availableFrom;
}

public struct Weight
{
    private static readonly Weight None = new Weight(-1);

    private int weightInKG;

    public Weight(int weightInKG)
    {
        this.weightInKG = weightInKG;
    }

    public WeightUnit Unit
    { get; set; }

    public string ToString()
    { /* Implement */ }
}

Then, if you have pieces of text that are localized (translated) then I'd just use strings. The factory class should get the appropriate localized string from the database. If there is no such string, use null.

public class Product
{
    private string ean;
    private Weight weight;
    private DateTime availableFrom;
    private string description;
}

Lastly, if you have information that is always used together localized (for example, specs for the product) then create a class Specs (or a hierarchy SpecsBase MonitorSpecs HardDiskSpecs if there are multiple kinds) for this. When there are none, use null. You can share these objects among multiple products if the information is the same. Again, the factory should take care of creating it.

You can also use these objects for fields that might be international or local depending on the installation.

public class Product
{
    private string ean;
    private Weight weight;
    private DateTime availableFrom;
    private string description;
    private Specs specs;
}

For all objects, override the ToString method to return the right (localized) strings.

3
  • Not sure this actually meets my point but I fear that was more due to how I asked the question than anything. I was really trying to figure out how to structure the Product class (or some form of subclass tree) so that it would give the possibility to have multiple values for each value within the Product itself. Something like ' public class InternationalProduct { public string Code {get; set;} public string Description {get; set;} public Dictionary<LocalProduct> Localized {get;set;} } public class LocalProduct { public string Description {get; set;} } ' }
    – Nick B.
    Aug 2, 2012 at 14:04
  • Something like public class InternationalProduct { public string Code {get; set;} public string Description {get; set;} public Dictionary<LocalProduct> Localized {get;set;} } public class LocalProduct : InternationalProduct { public string Description {get; set;} }
    – Nick B.
    Aug 2, 2012 at 14:12
  • Well, basically what I propose is a component architecture for you to have just a Product class with the local parts into a separate class that is added to the Product as a field. Of course, inheritance would also work if the graph is not too complex. Try to draw a graph of all the possible international/local product fields and combinations. If you have a cycle in there, you'll basically need components. Otherwise, its a tree and you can make it an inheritance tree. Or you might settle for a hybrid solution, combining the best of both. Aug 2, 2012 at 15:13
0

I'd just create a class with all the fields you need, then delegate instance creation to a factory. The factory will create instances of the class based on whatever locale criteria you discover is best (whether you specify the locale in the create method or specify a locale in the factory's constructor). The factory will hold all the logic for assembling objects from whatever tables you have. You can have different strategies for handling missing or default values (if these vary based on locale), and which fields are "localized". This concentrates all the logic in one place, yet gives you good flexibility.

1
  • Ok, so if I understand correctly, this would give instances of each a product with its local data which is part of the problem solved. I can get a product code "X1111" for each country, but would not maintain a relationship of Italy's "X1111" to Spain's "X1111", which is really what I am trying to solve. I would have to build into the factory a method to keep the "X1111" products linked together.
    – Nick B.
    Aug 2, 2012 at 13:06
0

Define a table for products which contains
ProductCode, ERP Description and other "constant" fields.

Define a table for properties which contains only 4 columns:
PropertyId(long),
PropertyOwnerId(long),
PropertyType(int, predefined in C# code, enumeration for instance),
PropertyValue(nvarchar, will be parsed depending on PropertyType in C# code, Object in C# code).

This is enough to bind the properties with thier owners. Later, if the properties change, all you have to do is insert new records to the second table.

The classes should have the same structure. This design is a little uncomfortable in sence that you'll have to parse a property read from DB or cast a property in C# code, but it is extremely flexible.

P.S if you think that some of the "constant" fields are likely to change, insert them as properties into the second table.

2
  • You are suggesting something like a hashmap for everything that is not clearly an international value, I understand the flexibility this gives and certainly solves the problem of having different instances of the same product for each country. I think that perhaps generics could get around the parsing issue but do you not think this might take a performance hit? I'm also slightly concerned that enumed properties might be less convenient for a developer than having clearly defined values. I'm not sure that the fields themselves will change with any great frequency.
    – Nick B.
    Aug 2, 2012 at 13:08
  • @Nick B., this approach can be changed by grouping properties into location-specific classes, if their quantity isn't large of course. For example, US, UK and Canada have the same group of additional attributes.
    – superM
    Aug 2, 2012 at 13:24
0

In the end I used a Product class with generic content and a detail class accessed through indexers to provide the functionality... but I'm really not sure its the right way to do it at all.

I didn't find a specific design pattern that explicitly defines how to do this (as per the original question). Except "Factory", but that seems bit generic...

This is how I implemented it (code has been simplified):

public class Product    
{   
    public string Code {get;set;}
    ....
    private Dictionary<string, ProductDetail> LocalProduct= new Dictionary<string, ProductDetail>();

    public LocalDetail this[string countryCode]
    {
        get
        {
            if (LocalProducts.ContainsKey(countryCode))
                return LocalProducts[countryCode];
            else
                throw new ApplicationException(String.Format("Local product detail does not exist for {0}", countryCode));
         }
         set
         {
            if (LocalProducts.ContainsKey(countryCode))
                throw new ApplicationException(String.Format("Local product detail already exists for {0}", countryCode));
            else
                LocalProducts.Add(countryCode, value);
        }
    }
}

public class LocalDetail 
{
    public string Description {get; set;}
    ....
}

The calling code is something like this :

        Product p = new Product("123456");

        LocalDetail UKDetail = new LocalDetail();
        UKDetail.Description = "Description in English";
        p["UK"] = UKDetail;            

        System.Console.WriteLine("Product {0} Description in UK Detail : {1}", p.Code, p["UK"].Description);

        LocalDetail ITDetail = new LocalDetail();
        ITDetail.Description = "Descrizione in Italiano";
        p["IT"] = ITDetail;
        System.Console.WriteLine("Product {0} Description in Italian Detail : {1}", p.Code, p["IT"].Description);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.