Recently I had the need to provide consumers of a SDK with a set of possible values that could be passed to a constructor of a member in a SDK library. The "list" of values was semi-static. With this, I mean that I also needed to treat this list of possible values as a "current version" and still allow the consumer to pass a somewhat arbitrary value. Why? Because the remote service could come to allow more values before the SDK has had a chance to be updated. There are (as always) many solutions, but lets look at one using simple factory methods and a implicit operator overloads.

The integration with the remote service is done using JSON. The command’s/request’s public getters are serialized and should preferably contain "primitive" types. Lets say we have a simple command/request object for creating a booking:

public class CreateBooking {
    public int TypeId { get; private set; }
    ...
    public CreateBooking(int typeId, ..., ...) {
        TypeId = typeId;
        ...
    }
}

The possible values of TypeId are currently as follows:

Gold: 3
Silver: 2
Bronze: 1

This could easily be solved with some simple "key-master" with constants. Or a simple enum, but thats not what we will pick. Instead, we will offer simple factory methods on a custom type BookingTypeId which will be used like this:

public class CreateBooking {
    public int TypeId { get; private set; }
    ...
    public CreateBooking(BookingTypeId typeId, ..., ...) {
        TypeId = typeId;
        ...
    }
}

A custom type being assigned to an integer? How? Easy. By the use of operator overloads. But before we take a look at them, lets answer the obvious questions:

Why not an enum or a key-master?

Enums would demand an explicit cast and they are static. Also, any logic to them, would have to be added as extension methods, and in the case I had, logic tied to the BookingTypeId was arround the corner.

Key-masters, like a static class with public constants, are not my favorite. Why? Bad discoverability. Because the argument to the constructor would then have to be an int, and there’s no easy way to hint the consumer of possible values as there is with a custom type. With this in mind, lets have a look at the solution.

The solution

First, we create a custom type that will be acceptet by the constructor of the CreateBooking class. By accepting a custom type, we give a clear hint of where to start looking for possible values.

public struct BookingTypeId {
    private readonly int _value;

    private BookingTypeId (int value) {
        //...some simple validation logic...
        _value = value;
    }

    public static BookingTypeId Custom(int value) {
        return new BookingTypeId(value);
    }

    public static BookingTypeId Gold() {
        return new BookingTypeId(3);
    }

    public static BookingTypeId Silver() {
        return new BookingTypeId(2);
    }

    public static BookingTypeId Bronze() {
        return new BookingTypeId(1);
    }
}

The constructor is being kept private because I want to force the user to discover the factory methods. To be able to pass "arbitrary" values I also provide a factory method for Custom values.

The final piece will be to have it implicitly convertible to an int.

public struct BookingTypeId {
    ...

    public static implicit operator int(BookingTypeId item) {
        return item._value;
    }
}

And while at it, lets add some interfaces to make it equatable and comparable to make the tests assertions work seamlesly.

public struct BookingTypeId : IEquatable<int>, IComparable<int> {
    ...

    public bool Equals(int other) {
        return _value.Equals(other);
    }

    public int CompareTo(int other) {
        return _value.CompareTo(other);
    }
}

That’s it! Done. Solved a problem for me. We can now do:

var goldBooking = new CreateBooking(BookingTypeId.Gold(), ..., ...);
//or
var customBooking = new CreateBooking(BookingTypeId.Custom(42), ..., ...);

That was all. Worked in my scenario. Not sure if you have any direct use for it right now. Perhaps something to put in the toolbox though?

Cheers,

//Daniel

Category:
Architecture, Design, C#, Development
Tags:

Join the conversation! 7 Comments

  1. Do you cover BookingTypeId with unit tests?

    Reply
  2. Sorry to be obtuse, but what do you mean by ‘key-master’? I can only think of Ghostbusters.

    Also, why have you used methods over properties? This seems to do just the same, and I would consider calling a method to create a parameter as a code smell:

    public static BookingTypeId Gold
    {
    get
    {
    return new BookingTypeId(3);
    }
    }

    Reply
  3. I’m not sure the omission of the cast from an enum is really a good idea – it’s likely to be a rare in code, and you wouldn’t want it to happen accidentally. On the other hand, enums are pretty limited, as you say – I’d be curious to see a more realistic use case that doesn’t map so naturally to enums.

    Minor code tweaks you could make:
    You should probably ensure that your struct’s default value (here: with value 0) has some sane interpretation. It’s quite easy for default initialization to sneak in somewhere.

    Also, since you’re essentially implementing an enum pattern, there’s no need to use the syntactically more verbose method pattern; you can simply use static readonly fields:

    public static readonly BookingTypeId
    Gold = new BookingTypeId(3),
    Silver = new BookingTypeId(2),
    Bronze = new BookingTypeId(1);

    This is mostly just shorter, but it also communicates to API users more clearly that there will not be any changes, nor any side-effects caused by retrieval.

    Reply
  4. […] Got some nice feedback and questions on a previous post of mine, and got to think things over about some design decisions, getting the possibility to change the […]

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: