µObjects: Class creation dilemma!

Working with µObjects at work and in the examples I've been experimenting with; I've found a recurring conflict of principles.

In the early days of learning actual Object Oriented Programming with Fred George we were hammered with "What's it's job?"! This is beaten into my head pretty well.

If an object has no behavior - it has no reason to exist.

Using the Pizza Kata Topping as the basis for this discussion.
Looking through the history of this file; we can see some of the variations; but we'll start with the base and discuss from there.

public class Topping : ITopping
{
    private readonly string _name;
    private readonly double _percentage;

    private Topping(string name, double percentage)
    {
        _name = name;
        _percentage = percentage;
    }

    public Money Cost(Money pizzaCost) => pizzaCost % _percentage;
    public IText Name() => new TextOf(_name);
}
public interface ITopping
{
    Money Cost(Money pizzaCost);
    IText Name();
}

With this; if I want to have a Mushroom topping; how do I do it?

Constructor Style

The stye that µObjects wants to use is the following

public class Mushroom : Topping {
    public Mushroom() : base("Mushroom", .1)
}

This screams in agony to me. The class has no behavior! It ... does nothing! The horror!

I'm against this - it's... empty and devoid of behavior.

Abstract Methods

The next version I've worked with; and is in a number of classes at work; is to use abstract methods.

public class Topping : ITopping
{
    public Money Cost(Money pizzaCost) => pizzaCost % Percentage();
    public IText Name() => new TextOf(Type());
    protected abstract double Percentage();
}
public class Mushroom : Topping {
    protected override string Type() => "Mushroom";
    protected override double Percentage() => .1
}

This way the Mushroom has a little behavior... ish. There's no public behavior. If there's no behavior... why does it exist?
(Note: We're ignoring that Name could be abstract and implemented in Mushroom for this discussion)

Caller Knows

We could force the knowledge on the caller...

new Topping("Mushroom", .1);

... But... uhh... That's spreading a bunch of const information all over. We're either having things hard coded and decreasing maintainability or adding public constants which... bleh. We don't use public constants.

This just doesn't work. It's a point that has been discussed, and has handidly been ruled out. If you want to do this to your code base - you're cruel.

Static Slugs

As has been done in the Pizza Kata, static slugs in Topping.

public static readonly ITopping Mushroom = new Topping("Mushroom", RegularPercentage);

This can work. Only if the object can exist as a single instance. This is nice because it hides how the object is instantiated.
A downside is that Topping now has quite a few reasons to change. The Description changes, the percentage changes, toppings added, toppings removed... This is a lot of reasons to change; and very much does not limit the scope of change - a key to maintainable code.

Slugs can work; They are critical to how Quantity and Conversion work. But it's not the correct choice everywhere. It's working (ish) in the Pizza kata; but it violates the princple of limiting the scope of change.

I don't like it, and what if we can't use the same instance everyhere?

Factory Methods

We could have methods on Topping that returns new instances for us instead of slugs

public class Topping : ITopping{
    public static ITopping Mushroom() => new Topping("Mushroom", .1);
}

This doesn't get us around the scope of change issue. Topping becomes a bit of a bloated and god-like class. It's horrible. It smells... It's static methods - Which we don't like static methods. Way too tightly couples things.
How do we get away from this horrible smell in the code.

Factory Object

There's two kinda factories I'm thinking about for thia. We can use a proper Factory class

public class ToppingFactory : IToppingFactory{
    public ITopping Create(...)...
}

But what the hell do we pass into the Create method? An enum? Gah - No. That's just asking for bad code to be written.
We could do specific functions

public class ToppingFactory{
    public ITopping CreateMushrooms() => new Topping("Mushroom", .1);
}

But then ... Ehh... We're starting to use factories. I feel factories are a symptom of a failed design. If you need a factory; your design has failed. It's not a good Object Oriented design. You should refactor until the factory isn't needed... Oh - Wait - That's exactly what we're trying to figure out how to do here!

Duplicate Code

We can simply duplicate the limited code.

public class Mushroom : ITopping
{
    public Money Cost(Money pizzaCost) => pizzaCost % .1;
    public IText Name() => new TextOf(Mushroom);
}

These types of classes in µObjects are going to be small. It's not a lot of code to be repeated. The downside here is that this is actually creating duplication. The code would change for the same reason. Each type of topping would need to be modified if the Cost calculation changed.
Duplication of Intent is bad - and this path creates A LOT of it. We can't allow that.

What to Do?

What do we do? I assume there's other options I'm missing; but this is all things we've discussed at work and I've explored through projects.

I had a discussion at lunch today which has actually clarified the best of these options.
It's the first one presented; passing the values to the base class.

public class Mushroom : Topping {
    public Mushroom() : base("Mushroom", .1)
}

Why are we accepting the empty class? The behaviorless abomination!

Because it does a thing.

This class does one thing; it knows one thing. It owns the information for the Mushroom topping.
We see something similar using the Chain of Responsibility; there's one clas that just knows how to construct the chain. This just knows how to construct the mushroom.
It looks really wrong. Just seeing it, my OOP sense screams - But... It is the µObject way. Do A Thing.

This is an aspect that I think deviates from other OOP style of development.

Summary

We've looked at a lot of ways we can instantiate objects that all use a shared behavior. They all have a smell, but the least smelly, most µObject style is to have an empty class that does one thing - provides the correct values. It's a very important task as we can see, and if it's not done properly; it'll harm the maintainability of the code.

My preferred options are going to be the Constructor style or Slugs when it's reasonible. Though I'm inclined to avoid the slugs unless there's a really good justification for it.

Show Comments