µObjects - What the hell are they?
What is a µObject (micro-object)? It's a tiny object. Not a small object with a single responsibility - A µObject does one thing.
Let me show with a simple example.
If we have a method with a guard clause and then it does something; say Write "Hello World".
class Sample{
void WriteNonEmptyString(string toWrite){
if(string.IsNullOrEmpty(toWrite)) return;
Console.WriteLine(toWrite);
}
}
This is a nice, concise method. Cyclomatic complexity of 2; and it does it's thing.
I see three µObjects here. There are three things happening here.
- Checking the input
- Writing
- A static method
What do I look for? My first pass to pull out µObjects is static methods.
Static methods are a tight coupling, we don't want this.
class Sample{
void WriteNonEmptyString(string toWrite){
if(new NullOrEmptyString(toWrite).Value()) return;
Console.WriteLine(toWrite);
}
}
class NullOrEmptyString{
private string _target;
NullOrEmptyString(string target) => _target = target
public bool Value() => string.IsNullOrEmpty(toWrite);
}
NullOrEmptyString
is transforming into a µObject. It's not yet. Let's finish the transformation of this into a µObject before we find the other two.
One of the huge wins of µObject is that they encourage immutability. Right now _target
could be changed. Let's make it readonly
for our C# example.
class NullOrEmptyString{
private readonly string _target;
NullOrEmptyString(string target) => _target = target
public bool Value() => string.IsNullOrEmpty(toWrite);
}
Now it's immutable and BAM - thread safety. Not done yet. What I focus on with µObjects is Best Practices. A lot do of styles do, obviously - one of them is to always program to the interface; we can't here - There's no interface!
interface IBooleanValue{
bool Value();
}
class NullOrEmptyString : IBooleanValue{
private readonly string _target;
NullOrEmptyString(string target) => _target = target
public bool Value() => string.IsNullOrEmpty(_target);
}
Now we have an interface. We'll ignore the naming for now. I have a different style based on Yegor's Cactoos project; but It'll distract from this discussion; so another post.
I hope I can hear you proclaiming, "We're not programming to the interface!"; and that is correct. We're not, but we'll get to that. First, looking at NullOrEmptyString
we can see a double purpose Null**Or**EmptyString
. It does two things. This isn't micro enough. There are instances we want to check for just null (not really; don't let null's into your system; which µObjects discourage nulls). Let's split this into two µObjects one for each actual check.
interface IBooleanValue{
bool Value();
}
class IsNullString: IBooleanValue{
private readonly string _target;
IsNullString(string target) => _target = target
public bool Value() => _target == null;
}
class IsEmptyString : IBooleanValue{
private readonly string _target;
IsEmptyString(string target) => _target = target
public bool Value() => string.IsEmpty(_target);
}
If we want to check both of these; we'll introduce a new class that does what the first class did
interface IBooleanValue{
bool Value();
}
class IsNullOrEmptyString: IBooleanValue{
private readonly string _target;
IsNullOrEmptyString(string target) : this(new IsNullString(target), new IsEmptyString(target))
IsNullOrEmptyString(params IBooleanValue[] checks) => _checks = checks;
public bool Value(){
foreach(IBooleanValue check in checks){
if(check.Value()) return true;
}
return false;
}
}
This is off the cuff; I don't particularly like the params
here; I'd prefer to turn into a Chain of Command pattern; but it works for what we have; so I'll accept it.
This is an glimpse into how µObjects encourage Composition over Inheritance. Build from pieces to combine functionality.
The IsNullOrEmptyString
class is operating only on interfaces; or in this case, singular. The IBooleanValue
.
But ... the instantiation of the concrete instances?! With µObjects; they instantiate everything they know they need - but are loosely coupled due to the class only having interface variables. This gives the object all the responsibility for it's own behavior. It prevents higher level components from knowing how this needs to be instantiated.
Doing something like the following; to me; makes zero sense.
class Sample{
void WriteNonEmptyString(string toWrite){
if(new IsNullOrEmptyString(new IsNullString(toWrite), new IsEmptyString(toWrite)).Value()) return;
Console.WriteLine(toWrite);
}
}
Why does sample need to know how IsNullOrEmptyString
actually implements it's behavior? It shouldn't. µObjects prevent this by having as close to a default constructor as possible that chains it's instantiation.
I call this style "Dependency Constructor". Maybe it has another name from someone else; haven't seen it.
Using Dependency Constructors allow our µObjects to discourage objects from knowing the details of what they use. If you take the other form all the way; your entry point will have a HUGE Russian Doll problem... which no ne will enjoy dealing with.
Back to our IsNullOrEmptyString
class; I'd normally eliminate the IsNull
component. µObjects discourage null; so we won't have to check for it most of the time.
I consider µObjects to be a great mechanism to refactor legacy code; Because of this I'll leave it in this example. The idea behind refactoring Legacy code; you need to maintain behavior. We don't know what else will be throwing crap at our new µObject code. What we will know; is that we can introduce the null
checking and prevent null
s from getting into deeper µObjects.
Our Sample
class now looks like
class Sample{
void WriteNonEmptyString(string toWrite){
if(new IsNullOrEmptyString(toWrite).Value()) return;
Console.WriteLine(toWrite);
}
}
We're still not programming to the interface in our Sample
class... and for the Boolean check... bad example for the post... I haven't found a good way to extract these types of checks into a Dependency Constructor.
We're still tightly coupled there. It's going to eat at me, I know it.
We pass the value into the constructor so that we create an immutable object with state. This can be passed around w/o evaluation and used in many contexts, where if we passed toWrite
into the Value
method, it limits the flexibility as we'd be passing a string
around instead of an 'IBooleanValue`. It's something I'm looking to improve, and figure out a better definition of baser objects like these that I use like the Cactoos library.
As for an instance we can... ish... use the Dependency Constructor on; turning Console.WriteLine into a class. This removes our static method.
class Sample{
void WriteNonEmptyString(string toWrite){
if(new IsNullOrEmptyString(toWrite).Value()) return;
new ConsoleWriter().WriteLine(toWrite);
}
}
interface IWriter{
void WriteLine(string toWrite);
}
class ConsoleWriter : IWriter{
void WriteLine(string toWrite){
Console.WriteLine(toWrite);
}
}
We now have a class implementing an interface that we can extract into a Dependency Constructor.
class Sample{
Sample():this(new ConsoleWriter()){}
Sample(IWriter writer) => _writer = writer;
void WriteNonEmptyString(string toWrite){
if(new IsNullOrEmptyString(toWrite).Value()) return;
_writer.WriteLine(toWrite);
}
}
The result isn't smaller. It's a couple extra lines. But we've removed the dependency on the Console
. It's now 100% testable.
I'm working on a way to extract the new IBooleanValue
; but not seeing a clear way. I'd rather have an obvious smell than a hidden one.
The final result is much bigger code and class wise
class Sample{
Sample():this(new ConsoleWriter()){}
Sample(IWriter writer) => _writer = writer;
void WriteNonEmptyString(string toWrite){
if(new IsNullOrEmptyString(toWrite).Value()) return;
_writer.WriteLine(toWrite);
}
}
interface IWriter{
void WriteLine(string toWrite);
}
class ConsoleWriter : IWriter{
void WriteLine(string toWrite){
Console.WriteLine(toWrite);
}
}
interface IBooleanValue{
bool Value();
}
class IsNullString: IBooleanValue{
private readonly string _target;
IsNullString(string target) => _target = target
public bool Value() => _target == null;
}
class IsEmptyString : IBooleanValue{
private readonly string _target;
IsEmptyString(string target) => _target = target
public bool Value() => string.IsEmpty(_target);
}
class IsNullOrEmptyString: IBooleanValue{
private readonly string _target;
IsNullOrEmptyString(string target) : this(new IsNullString(target), new IsEmptyString(target))
IsNullOrEmptyString(params IBooleanValue[] checks) => _checks = checks;
public bool Value(){
foreach(IBooleanValue check in checks){
if(check.Value()) return true;
}
return false;
}
}
It's a bit of a class explosion. The advantage; We now have 4 re-usable classes. This is what re-usable code looks like. I've spent nearly 20 years writing "OO" code; and barely had a single class I could reuse in another project. µObjects build up a library of objects you can use all over. Extracting these baser functional objects from the business logic gives you things you can leverage in many different places in the same project; and are re-usable between projects.
Update
As I mentioned; having that tight coupling would eat at me. Trying to justify something in a blog post... can't. I need to find a solution.
Here though; I think I need to find the least smelly solution.
What's the actual problem? Tight coupling of the IsNullOrEmptyString
class in code. We should only use interfaces. We shouldn't new
in the code.
What ways could I fix this?
Not having it. This puts us back into the state of the class knowing how to do two things; write a nonnull/empty string AND check if it's not null or empty.
Make it static. Then it's not an object. It's a static method with tight coupling. What's the static, a StringUtils
? - No.
Make Value
take args. This makes the object enact the operation at that point. It's no longer able to be passed. The object isn't anything. It's an instantiated static.
Make it a hidden static. In next weeks post; I "hide" that I'm operating on a null. Here I push that null down into a single point. null
as a concept doesn't float about the code; but it's there; underlying. It smells... just... less. How, without being tightly coupled to a class?
C# has Extension methods. Using an extension method to do the instantiation of the IsNullOrEmptyString
class and return IBooleanValue
gives us a highly decoupled method to that removes the inline new
and gives us a builder method.
What will the extension method look like? Let's do it!
public static StringExtensions{
public static IBooleanValue CheckNullOrEmpty(this string source) { return new IsNullOrEmptyString(source);}
}
The use of this looks like
class Sample{
Sample():this(new ConsoleWriter()){}
Sample(IWriter writer) => _writer = writer;
void WriteNonEmptyString(string toWrite){
if(toWrite.CheckNullOrEmpty().Value()) return;
_writer.WriteLine(toWrite);
}
}
This gives us a bit more flexibility in how to implement some features.
class IsNullOrEmptyString: IBooleanValue{
private readonly string _target;
IsNullOrEmptyString(string target) : this(new IsNullString(target), new IsEmptyString(target))
IsNullOrEmptyString(params IBooleanValue[] checks) => _checks = checks;
public bool Value(){
foreach(IBooleanValue check in checks){
if(check.Value()) return true;
}
return false;
}
}
We could transform it into
public static class StringExtensions{
public static IBooleanValue CheckNullOrEmpty(this string source) { return new IsNullOrEmptyString(source);}
public static IBooleanValue CheckNullString(this string source) { return new IsNullString(source);}
public static IBooleanValue CheckEmptyString(this string source) { return new IsEmptyString(source);}
}
class IsNullOrEmptyString: IBooleanValue{
private readonly string _target;
IsNullOrEmptyString(string target) : this(target.CheckNullString(), target.CheckEmptyString())
IsNullOrEmptyString(params IBooleanValue[] checks) => _checks = checks;
public bool Value(){
foreach(IBooleanValue check in checks){
if(check.Value()) return true;
}
return false;
}
}
and because I dislike the same argument types; and that the IsNullOrEmpty isn't enforcing a "null or empty" check; but can take anything...
class IsNullOrEmptyString: IBooleanValue{
IsNullOrEmptyString(string target) => _target = target;
public bool Value(){
return _target.CheckNullString().Value() || _target.CheckEmptyString().Value();
}
}
Now it only does the null or empty; can't do anything else.
Caveat : I caveat this that it's an extreme step. This isn't to allow anything to be put into an extension method. It's not to simplify code - simpler code is another object. This is to remove the new smell when it's the least smelly way.
For exploration; what if we created a "StringExtUtils" class? Basically the same class; but with an interface so we aren't dependent on the class... First issue that springs to mind is our "contract" changes every time we add a new method. Not horrible... but annoying.
What's it gonna look like...
public class StringBoolChecks : IStringBoolChecks {//Assume all methods are in the interface
public IBooleanValue IsNullOrEmpty(this string source) { return new IsNullOrEmptyString(source);}
public IBooleanValue IsNullString(this string source) { return new IsNullString(source);}
public IBooleanValue IsEmptyString(this string source) { return new IsEmptyString(source);}
}
class IsNullOrEmptyString: IBooleanValue{
IsNullOrEmptyString(string target) this(target, new StringBoolChecks())
IsNullOrEmptyString(string target, IStringBoolChecks ext) => _target = target, _ext=ext;//yeah yeah
public bool Value(){
return _ext.IsNullString(target).Value() || _ext.IsEmptyString(target).Value();
}
}
After some experimenting with this in a work code-base; it removes the smell very well. Still a bit of an oddity in the code; but I think it turns out better for the boolean cases I'm dealing with.