Thursday, April 9, 2015

The Compatibility Pattern: Designing adaptive classes

Any non-deprecated system, soon or late, faces challenges. Some of these challenges are quite common. The two following items represent just a fraction of those:
  • existing file storage doesn't satisfy current/coming space requirements
  • the data model need to be modified/extended to comply with new requirements

Today I’m going to describe a design pattern, which can be applied in a variety of situations (including the two above) and eliminate the pain of code re-write.

So, for the sake of simplicity let’s concentrate on a single scenario. Let’s assume we have an existing system – which is operational. Let’s also assume that “EntityA” is an entity in the data model of the system, and the records of that entity are processed by some business logic: “LogicA”.  So far the system used to operate just fine. Now – new requirements come in – which introduce changes to “EntityA” and the “LogicA” as well. Specifically, new “EntityA” records should be treated differently than the “old/current” records. For the sake of simplicity, let’s limit the changes in “EntityA” by just one new field “Field2”. So here what “EntityA” will look like:


where “Field1” is an existing field on the entity. Note, that I intentionally don’t mention the types here as its irrelevant in this context.

Also, let’s assume that “LogicA” is already encapsulated in a class with the same name, as follows:


Now, back to the new requirements, which affect “LogicA”. The simplest way to handle the change would be to modify the “LogicA.ProcessData” method. However that won’t be maintainable. Think about any future changes – affecting the same logic. After several such changes it’ll be really a mess and it won’t be easy to figure out how to change something  to impact only specific version of entities. So the simplest and design-wise proper solution would be to introduce new LogicA_New handler for the new entities and put the decision logic of instantiating the proper logic in some factory class. The following diagram depicts that case:


Note, that the abstraction of the handling logic into separate classes is an implementation of the Strategy design pattern.

This model allows future expansion of the system by just defining a new strategy/logic and updating the factory method, to return the proper handler type. Handling logic deprecation is quite straight forward as well, and can be handled in the reverse order: the factory method is updated to remove the obsolete case and the appropriate logic handler is removed.

Hope this will save you some time in the future.

No comments: