What principle of OOAD is this pattern breaking?

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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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());
}
}
</code>
<code>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()); } } </code>
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):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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());
}
}
</code>
<code>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()); } } </code>
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

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật