I’m trying to make a case for not putting the structure in the parent BaseModule class I’ve shown below. I’m more for a Strategy Pattern, and minimizing inheritance in favor of has-a relationships, but I am not really sure of what principle of OOAD this pattern is breaking.
What principle of OOAD is this pattern breaking?
Potential Pattern:
public interface IStringRenderer
{
string Render();
}
public class BaseModel : IRequestor
{
public abstract void Init();
public abstract void Load();
}
public class BaseModule<TModel> : IStringRenderer where TModel : BaseModel
{
public TModel Model { get; set; }
public override string Render()
{
return
string.Join(
"",
new string[]
{
"<header/>",
"<main-container>",
"<side-nav>",
"<side-navigation/>",
"</side-nav>",
"<main-content>",
RenderMainContent(),
"</main-content>"
});
}
public abstract string RenderMainContent();
}
public class MyModuleModel : BaseModel
{
public List<int> Ints { get; set; }
...
}
public class MyModule : BaseModule<MyModuleModel>
{
public override string RenderMainContent()
{
return
string.Join(",", Model.Ints.Select(s => s.ToString()).ToArray());
}
}
Preferred Pattern (duplicated some code to be clear):
public interface IStringRenderer
{
string Render();
}
public class BaseModel : IRequestor
{
public abstract void Init();
public abstract void Load();
}
public class MyModuleModel : BaseModel
{
public List<int> Ints { get; set; }
...
}
public class MyModule : IStringRenderer
{
public MyModuleModel Model { get; set; }
public MyModule(MyModuleModel model)
{
this.Model = model;
}
public override string Render()
{
return
string.Join(
"",
new string[]
{
"<header/>",
"<main-container>",
"<side-nav>",
"<side-navigation/>",
"</side-nav>",
"<main-content>",
RenderMainContent(),
"</main-content>"
});
}
public string RenderMainContent()
{
return
string.Join(",", Model.Ints.Select(s => s.ToString()).ToArray());
}
}
There are parts of this pattern that I’m not trying to focus on, but instead I am thinking about where the ‘structure’, or the ‘tags’ are living. In the first example they live in the parent, but in the second example they have been pushed into the derived class.
Right now, the examples are pretty simple, but something like ‘side-nav’ could potentially get complicated, and may need to be controlled by the child anyway.
I feel the principle here is that I don’t feel like the ‘tag’ structure shouldn’t be in the parent class as in the first example. There are other things I’ve removed in my ‘preferred version’ — namely the generics. (Any suggested reading on good/bad for that choice is welcome). Proponents of the first example’s embedded structure like the fact that they can wield changes to large amounts of child classes. I feel offering the flexibility to derived classes is the way to go, and if there needs to be a parent that parent only offers functionality and cross-cutting features, but does not bottle up canned structure.
3
You can have the best of both worlds. In the BaseModule<TModel>
class, when you implement (you typed override
there, which is not correct) the Render()
method, you can flag it as virtual
. This way, child classes that need a basic set of tags can simply use the base class Render()
, while child classes with more peculiar needs may override it.
About the generics, you are not using them in the BaseModule<>
… unless you are using it in a constructor (not shown) that initializes the Model
property for all derived class maybe? In that case they have their use here, since they constrain the type you can pass in and they un-burden the derived classes from having to initialize it themselves. This is more like a “framework” approach, where an abstract
base class is used only to provide common but overridable functionality to derived concrete classes.
Overview
It sounds similar to CommonClosurePrinciple, but happens on a class-and-methods level instead of a package-and-classes level. Namely,
–Classes– (methods) within a released –component– (a class) should share common closure. If one (method) needs to be changed, they all (methods) are likely to need to be changed. What affects one, affects all.
(I added the words in brackets.)
Also, the principle that can be gleaned from this code example is a weak one, meaning that it is not universally applicable. There are obviously cases where changes don’t / shouldn’t need to be concentrated into a single class!
In this particular question, the validity of this principle depends on whether the (1) HTML tags and (2) the model-to-string code are typically changed together when there is a change in model.
The Code
In both versions, MyModule
is the place for the glue logic that exists between Model
and IStringRenderer.Render()
.
In the first version, the HTML tags (<main-content>...</>
) is a service provided by BaseModule
.
- If the same wrapping code is reused many times for many different models, this is a correct choice.
- If each model ended up having to customize its HTML tags, then the
BaseModule
class in the first version will only get used once.
In the second version, both pieces of glue logic are contained in the leaf class (MyModule
):
- The conversion of integers (model) to a string, and
- The HTML tags
Thus, the second version allow the model rendering and the HTML wrapping code to be changed together.
Opinion:
If it occurs to you that each model require different HTML tags, then I’d say that the first version is not sufficient, while the second version is. Otherwise, both versions are fine.
If one must look for a name for the principle, it would be along the line of “… methods that need to be changed together for the same reason (changing the model), would be nice to be placed into the same class.”
(I emphasize “would be nice” because it is a weak principle, or, a rule-of-thumb and not a true principle.)
It sounds similar to CommonClosurePrinciple, but applied to a class-and-methods level instead of a package-and-classes level.
1