Technical Practice: Objects avoid Primitive Obsession

I have a GitHub project FluentTypes which I've written about before.

The goal is to avoid the problems of Primitive Obsession. We want to represent what we're thinking.

After working in MicroObjects style and using the style represented by the Fluent Types; I found a new way. To my credit, I mention this in the earlier fluent types post. I was never satisfied with using my custom Text object.
It was BETTER than string. Far and away better. It held a slightly better representation of what we were doing in the system than string. It eliminated the "does too much" problem. It removed doing string manipulation inline. All of these behaviors are captured in objects; which is exactly what we want to do.

In the few projects this had been employeed on, worked well. Text was still getting passed around everywhere. Which is what we want to avoid in Primitive Obsessions. I removed a lot of the problems I see when using primitives, but I was still passing Text around everywhere. It lost it's meaning.

The code no longer told us what Text was... we'd have to figure it out. We did pretty good representing a lot of objects by type; often they'd have a way to get a Text back out. Or the values from JSON were all just Text objects.

For a first pass - I think we did really well at getting rid of primitives. :) It clearly helped us excel at delivering quickly with highly maintainable code.

Trying something new

When I rolled onto another project I'd been trying to find a way to get rid of the 'itch' that Text gave me. It threaded through the system as a final result, but not as the object it represented.

It was about 6 months ago that I tried to use the actual Object. I still remember the class, "Document Control Number". We had an IDcn chain of responsibility that returned a text. The Chain built up the DCN out of a half dozen steps and gave back the final string.

I finally had enough, "What if we just use the object?". Let's not have AsText to get back a Text that builds it. Let's just pass around our IDcn. We'll give the DocumentControlNumber the implicit string cast operation.
This kills the ability to use interfaces and forces us into abstract classes; BUT - I'll accept that to be able to have object representation of what we're actually dealing with.

I'd now have an abstract class Dnc class and my chain head is sealed class DocumentControlNumber : Dcn.

Dcn and DocumentControlNumber don't actually have methods. The class is behaviorless except for the implicit cast to a string.
Let's put this into full code

abstract class Dcn{
    public static implicit operator string(Dcn origin) => origin.RawValue();
    
    protected abstract string RawValue();
}

sealed class DocumentControlNumber : Dcn{
    private readonly IDcnAction _nextStep;
    
    public DocumentControlNumber(): this(
        new DcnStep3(new DcnStep2(new DncStep1()))){};
    private DocumentControlNumber(IDcnAction nextStep) => _nextStep = nextStep;
    
    protected override string RawValue() => _nextStep.Act();
}

interface IDncAction{
    Text Act();
}

Dcn's only behavior is converting to a string. DocumentControlNumber only knows how to order the steps to create a document control number. Each IDncAction only knows how to do it's one task. The Fluent Types are still useful for this micro-scale manipulation. If it was just simple string concatination, each step would do something like

return _nextAction.Act().Concat("step value");

We don't need to see HOW concatination works. We don't need to know ANY of the detailed string manipulation details. We don't know the underlying data type or structure - ignorance is bliss.

What the DocumentControlNumber sets us up to do is be able to safely instantiate the object, Delay execution of calculating the value incase of some costly bit, not know any of the details, and still use it with 3rd party systems that want a string.

It IS what we want. It can be interacted with appropriately (in this case, not). It can still be passed to 3rd party systems without ugly casts or (visible) method calls.

Better Model

The way I created the Document Control Number object represents a better way to avoid primitive obsession. I called it out in my fluent types post that object representation is a better way to avoid Primitive Obsession - This is my example of it.

Todays a great day to be writing this post (2019/02/15) even though it won't be posted for a while. The project I was on while developing this particular class had me pairing with a co-worker who's last day is today.

Summary

While it's going to be a challenge to start to wrap one's mind around how to /actually/ model objects in our code; when we can do that - the code is far less complex. Behaviors tend to be easier to place. Maintainability is less costly. New features easier to implement. Changes have a very narrow scope.

I hope this helps provide a small glimpse into how to move forward with Object representation.

Show Comments