Categories
Uncategorized

Using Abstract Classes to Simplify Common ScriptLink OptionObject Tasks

In my last post, we looked at using interfaces to support the multiple versions of the OptionObject without writing duplicated code. Interfaces are great for creating methods that can consume or return multiple concrete classes such as our OptionObjects. However, in C# at least, you can’t implement any common functionality through interfaces. They are simply contracts that define what the concrete classes are to implement. What if there was a common method that we wanted to extend our OptionObjects with? Welcome abstract classes.

In my last post, we looked at using interfaces to support the multiple versions of the OptionObject without writing duplicated code. Interfaces are great for creating methods that can consume or return multiple concrete classes such as our OptionObjects. However, in C# at least, you can’t implement any common functionality through interfaces. They are simply contracts that define what the concrete classes are to implement. What if there was a common method that we wanted to extend our OptionObjects with? Welcome abstract classes.

Abstract classes are similar to interfaces in that concrete class can inherit them, except the provide a common set of properties and methods that the concrete classes will have access to if they inherit from them. The concrete classes can even override these common items when they require a special case. In our ScriptLink project, we have one use case already where it would be nice to simplify our code: returning an OptionObject.

The Setting

If we look at our DefaultCommand and our HelloWorld command, you will notice that in both we write common code to create that return OptionObject with the appropriate Error Code and Message. Take a look at our Execute methods below and see how many lines are identical to each other.

DefaultCommand

public IOptionObject2015 Execute()
{
    string message = "Error: There is no command matching the parameter '" + _parameter + "'. Please verify your settings.";

    return new OptionObject2015()
    {
        EntityID = _optionObject2015.EntityID,
        EpisodeNumber = _optionObject2015.EpisodeNumber,
        ErrorCode = 3,
        ErrorMesg = message,
        Facility = _optionObject2015.Facility,
        NamespaceName = _optionObject2015.NamespaceName,
        OptionId = _optionObject2015.OptionId,
        OptionStaffId = _optionObject2015.OptionStaffId,
        OptionUserId = _optionObject2015.OptionUserId,
        ParentNamespace = _optionObject2015.ParentNamespace,
        ServerName = _optionObject2015.ServerName,
        SystemCode = _optionObject2015.SystemCode,
        SessionToken = _optionObject2015.SessionToken
    };
}

HelloWorld Command

public IOptionObject2015 Execute()
{
    return new OptionObject2015()
    {
        EntityID = _optionObject.EntityID,
        EpisodeNumber = _optionObject.EpisodeNumber,
        ErrorCode = 3,
        ErrorMesg = "Hello, World!",
        Facility = _optionObject.Facility,
        NamespaceName = _optionObject.NamespaceName,
        OptionId = _optionObject.OptionId,
        OptionStaffId = _optionObject.OptionStaffId,
        OptionUserId = _optionObject.OptionUserId,
        ParentNamespace = _optionObject.ParentNamespace,
        ServerName = _optionObject.ServerName,
        SystemCode = _optionObject.SystemCode,
        SessionToken = _optionObject.SessionToken
    };
}

The Goal

Given the repetition of the above code and knowing that if we add any more commands we will be writing this code over and over, let’s simplify it.

A common approach is to create a utility class that you can pass the OptionObject to and it will return the expected return OptionObject. Let’s say the utility class is named OptionObjectHelper, then this method would look something like this.

public IOptionObject2015 Execute()
{
    return OptionObjectHelper.GetReturnOptionObject(_optionObject, 3, "Hello, World!");
}

Notice the class accepts the OptionObject to base the return OptionObject off of, the Error Code, and Message. A utility class like this does exist in the AvatarScriptLink.NET NuGet package to users to simplify their management of OptionObjects. It also works as described above. However, we are going to explore another strategy that extends our OptionObject itself.

public IOptionObject2015 Execute()
{
    return _optionObject.ToReturnOptionObject(3, "Hello, World!");
}

In this design, the OptionObject itself gains the ability to create the return OptionObject and we save a few keystrokes. This design is also supported by AvatarScriptLink.NET.

To accomplish this we could add the ToReturnOptionObject method to each concrete OptionObject or we can use an abstract class and write it once. So let’s try it out.

Creating the Abstract Class

In our Objects.Advanced folder let’s create a new file named OptionObjectBase.cs. Once created we are going to inherit the IOptionObject2015 interface like we did with the concrete classes and implement the required properties. It should look like this.

public abstract class OptionObjectBase : IOptionObject2015
{
    public string EntityID { get; set; }
    public double EpisodeNumber { get; set; }
    public double ErrorCode { get; set; }
    public string ErrorMesg { get; set; }
    public string Facility { get; set; }
    public List<FormObject> Forms { get; set; }
    public virtual string NamespaceName { get; set; }
    public string OptionId { get; set; }
    public string OptionStaffId { get; set; }
    public string OptionUserId { get; set; }
    public virtual string ParentNamespace { get; set; }
    public virtual string ServerName { get; set; }
    public string SystemCode { get; set; }
    public virtual string SessionToken { get; set; }
}

I have marked each of the OptionObject2015 properties not included in the OptionObject as virtual to allow us to override them later. I explain when we get there.

Ok. Now let’s create our method. It is nearly a copy and paste of what we are using in our commands.

public abstract class OptionObjectBase : IOptionObject2015
{
    public string EntityID { get; set; }
    public double EpisodeNumber { get; set; }
    public double ErrorCode { get; set; }
    public string ErrorMesg { get; set; }
    public string Facility { get; set; }
    public List<FormObject> Forms { get; set; }
    public virtual string NamespaceName { get; set; }
    public string OptionId { get; set; }
    public string OptionStaffId { get; set; }
    public string OptionUserId { get; set; }
    public virtual string ParentNamespace { get; set; }
    public virtual string ServerName { get; set; }
    public string SystemCode { get; set; }
    public virtual string SessionToken { get; set; }

    public OptionObjectBase ToReturnOptionObject()
    {
        return ToReturnOptionObject(0, null);
    }

    public OptionObjectBase ToReturnOptionObject(double errorCode, string errorMesg)
    {
        ErrorCode = errorCode;
        ErrorMesg = errorMesg;
        return this;
    }
}

A few noteworthy items to mention.

  1. First, we are changing our return from the interface to our abstract class. This means that the object returned will include any methods we add to our abstract class and not just the required properties identified by the interface. Not critical here but definitely so in other use cases.
  2. Second, for simplicity, we are returning the same object with the ErrorCode and ErrorMesg set. Optionally, we could clone the OptionObject. This change–versus new-ing an OptionObject–enables all OptionObjects to be returned from this method.
  3. Third, a override of the ToReturnOptionObject method is added to allow creating a successful return without having to pass a code and message. The value of this will be seen in a future article.

In general, I like to enable future use cases when I code rather than block them. Those two small changes now could save a lot of headache later.

At this point our code should compile and our unit tests still pass, because we have written valid code and nothing uses it yet. So let’s change that.

Update the Concrete Classes

To implement our abstract class, we will go to each of our concrete classes and change them to inherit the abstract class instead of the interface. For example:

public class OptionObject : OptionObjectBase

You will notice that once you change the inheritance each of the declared properties now throw a warning asking us to use the new keyword if we are really intending to hide the abstract implementation. This means that we no longer have to declare the properties in our concrete classes, except for the properties we need to use [XmlIgnore] with.

Go ahead and remove each property that doesn’t use [XmlIgnore] and add override to tell the compiler that we do wish to override the behavior. This is possible because we marked them as virtual earlier. Our new classes should look like these.

public class OptionObject2015 : OptionObjectBase
{
    // All properties provided by Abstract class
}
    public class OptionObject2 : OptionObjectBase
    {
        [XmlIgnore]
        public override string SessionToken { get; set; }
    }
public class OptionObject : OptionObjectBase
{
    [XmlIgnore]
    public override string NamespaceName { get; set; }
    [XmlIgnore]
    public override string ParentNamespace { get; set; }
    [XmlIgnore]
    public override string ServerName { get; set; }
    [XmlIgnore]
    public override string SessionToken { get; set; }
}

Ok. The abstract class is now implemented and our concrete class simplified. We should be able to compile and see our unit tests pass. Next, to implement our new method.

Implementing Our Method

Let’s go to our HelloWorld command first. If everything is good then we should be able to implement our intended design from above.

public IOptionObject2015 Execute()
{
    return _optionObject.ToReturnOptionObject(3, "Hello, World!");
}

Yet this doesn’t work yet. You should get an error stating that “‘IOptionObject2015’ does not contain a definition for ‘ToReturnOptionObject’ and no accessible extension method ‘ToReturnOptionObject’ accepting a first argument of type ‘IOptionObject2015’ could be found (are you missing a using directive or an assembly reference?)”.

This is correct, our interface doesn’t know about that method. Our abstract class does though. We need to change our injected interface references to our new abstract class. However, we will still return the interface. Like so:

public class HelloWorld : IRunScriptCommand
{
    private readonly OptionObjectBase _optionObject;

    public HelloWorld(OptionObjectBase optionObject)
    {
        _optionObject = optionObject;
    }

    public IOptionObject2015 Execute()
    {
        return _optionObject.ToReturnOptionObject(3, "Hello, World!");
    }
}

Let’s do the same for our DefaultCommand as well.

public class DefaultCommand : IRunScriptCommand
{
    private readonly OptionObjectBase _optionObject2015;
    private readonly string _parameter;

    public DefaultCommand(OptionObjectBase optionObject2015, string parameter)
    {
        _optionObject2015 = optionObject2015;
        _parameter = parameter;
    }

    public IOptionObject2015 Execute()
    {
        string message = "Error: There is no command matching the parameter '" + _parameter + "'. Please verify your settings.";

        return _optionObject2015.ToReturnOptionObject(3, message);
    }
}

Excellent. We have our abstract class implemented and our commands have no more errors, but we still can’t compile. Now our CommandSelector factory has a conversion issue.

Updating Our CommandSelector Factory

The issue we are having is that an IOptionObject2015 cannot implicitly convert to an OptionObjectBase. That conversion must be explicit. The simplest way to approach this to add an explicit declaration like this.

public static IRunScriptCommand GetCommand(IOptionObject2015 optionObject, string parameter)
{
    switch (parameter)
    {
        case "HelloWorld":
            return new HelloWorld((OptionObjectBase)optionObject);
        default:
            return new DefaultCommand((OptionObjectBase)optionObject, parameter);
    }
}

This could become annoying as more commands get added so we can do the conversion once instead, like this.

public static IRunScriptCommand GetCommand(IOptionObject2015 iOptionObject, string parameter)
{
    OptionObjectBase optionObjectBase = (OptionObjectBase)iOptionObject;

    switch (parameter)
    {
        case "HelloWorld":
            return new HelloWorld(optionObjectBase);
        default:
            return new DefaultCommand(optionObjectBase, parameter);
    }
}

I’m not excited about the new variable names but they do help illustrate what is happening here. Now we should be able to compile and run our unit tests. Did that work? If so, run it and see if you get your expected results in SoapUI or Postman.

Next Steps

We’re getting close to the end of my getting started with ScriptLink series. Once this series is finished, I will start demonstrating how to migrate this solution to AvatarScriptLink.NET and start a new project based on that NuGet package. Are there any topics you would like me to cover? Any examples you would like to see demonstrated? Please let me know.

Update 2020-05-03: A bug in the original draft preventing use of the ToReturnOptionObject method with the legacy OptionObject versions. The article has been updated to reflect that fix.

Advertisement