Saturday 25 March 2017

Generic Pattern: How to do Fluent-API on both Derived and Base classes

I would like to share a lesson learned from work. I am working on a FHIR (Fast Healthcare Interoperability Resources) project. The task at hand was to create builder classes, which basically populate properties of different kinds of resource objects (e.g Patient, Order, Schedule , etc). All these resources are derived from the same object called Resource, which comprises properties such as Id, Meta (contains VersionId and Profile). The derived classes may have their own specialization properties/fields.

My intention is to create a builder class for each of the derived class, however all these builder classes will share the same code for populating properties belong to the base class Resource. The best logical approach to create a base abstract class, call it ResourceBuilder , to implement the shared code. It is also logical to apply generic pattern to this base builder class, with a generic parameter which should be the resource itself.

Additionally, I want to apply fluent API to these builder classes, so that these objects can continually populate properties after properties without requiring to break into a new statement. For simplicity, the base builder class, ResourceBuilder will only two methods, SetId(string id), and Build(). The derived builder classes, PatientBuilder, will have a additional method called SetName(string name). Note, the content of the code have been simplified for readability purposes. For the first iteration, our code will be like below:
        
public abstract class ResourceBuilder<TResource> where TResource : Resource, new() 
{
  protected TResource _resource;  
  protected ResourceBuilder(TResource resource)
  {
    _resource = resource;
  } 

  public ResourceBuilder SetId(string id)
  { 
    _resource.Id = id;
    return this;
  }

  public TResource Build()
  {
    return _resource;
  } 
}

public class PatientBuilder : ResourceBuilder<Patient>
{
  public PatientBuilder(): base(new Patient())
  {
  }

  public PatientBuilder SetName(string name)
  {
    _resource.Name = name;
    return this;
  }
}

public class Main()
{
  public void CallingMethod()
  {
    Patient patient = new PatientBuilder()
      .SetId("1") //set Resource properties, this method belong to ResourceBuilder
      .SetName("Richard") //set Patient properties, this method belongs to PatientBuilder
      .Build();
  }    
}

The code looks good, except that it does not compile. The problem is that after SetId("1"), the method will return ResourceBuilder instead of PatientBuilder, and ResourceBuilder does not recognise SetName("Richard"), which belongs to PatientBuilder.

One way to get around this issue is to set all Patient properties first, before set the base, Resource, properties. However doing this way means we put some implicit restriction on how we are going to use the builder classes, and this is not convenient.

Another way to do this is to pass the derived builder class, PatientBuilder, to the base builder class, ResourceBuilder in the generic pattern, so the base builder class can return the derived builder class in their set methods. Here is the code after the changes:
        
public abstract class ResourceBuilder<TResource, TDerivedResourceBuilder> 
  where TResource : Resource, new() 
where TDerivedResourceBuilder : ResourceBuilder<TResource, TDerivedResourceBuilder>
{
  protected TResource _resource;  
  protected ResourceBuilder(TResource resource)
  {
    _resource = resource;
  } 

  public TDerivedResourceBuilder SetId(string id)
  { 
    _resource.Id = id;
    return (TDerivedResourceBuilder)this;
  }

  public TResource Build()
  {
    return _resource;
  } 
}

public class PatientBuilder : ResourceBuilder<Patient, PatientBuilder>
{
  public PatientBuilder(): base(new Patient())
  {
  }

  public PatientBuilder SetName(string name)
  {
    _resource.Name = name;
    return this;
  }
}

public class Main()
{
  public void CallingMethod()
  {
    Patient patient = new PatientBuilder()
      .SetId("1") //set Resource properties, this method belong to ResourceBuilder
      .SetName("Richard") //set Patient properties, this method belongs to PatientBuilder
      .Build();
  }    
}

And now the code compiles. The code changes made are emphasized below:
        
public abstract class ResourceBuilder< .., TDerivedResourceBuilder> 
 ..
where TDerivedResourceBuilder : ResourceBuilder<TResource, TDerivedResourceBuilder>
{
 ..
  public TDerivedResourceBuilder SetId(string id)
  { 
    ..
    return (TDerivedResourceBuilder)this;
  }

..
}

public class PatientBuilder : ResourceBuilder<.., PatientBuilder>
{
 ..
}

First, the PatientBuilder is passed to base builder class definition in the PatientBuilder class definition.
 
public class PatientBuilder : ResourceBuilder<.., PatientBuilder>
{
 ..
}

Secondly, the base builder class, ResourceBuilder, now have access to the derived builder class, and can use it in their set methods
    
 public TDerivedResourceBuilder SetId(string id)
  { 
    ..
    return (TDerivedResourceBuilder)this;
  }
And now the calling method compiles fine and works.

No comments:

Post a Comment