DEV Community

Dhana
Dhana

Posted on

Design Patterns in a Real Project: One I Actually Had, and One I Thought I Had But Didn't

Design pattern articles often present clean, already-perfect examples: here's the pattern, here's the code, here's why it's elegant. Real codebases are messier than that, and recognizing a pattern accurately — including recognizing when something isn't actually a pattern, just plain code that happens to look similar — is a more useful skill than memorizing textbook definitions. This article walks through two patterns I went looking for in an actual enterprise .NET project: one I genuinely had, and one I initially thought I had, until a closer look showed I didn't.

Pattern One: The Repository Pattern, Already There Without the Name

The project in question uses a familiar structure: a Controller layer, and a Data Access layer accessed through an interface.

csharp
public interface IclsWorkFlow
{
List GetAddWorkFlows(int employeeSlno, int createdBy);
bool SaveAddWorkFlows(List models);
}

public class clsWorkFlow : IclsWorkFlow
{
public List GetAddWorkFlows(int employeeSlno, int createdBy)
{
// ADO.NET / Oracle-specific logic lives here
}

public bool SaveAddWorkFlows(List<UserRoleMappingModelDto> models)
{
    // ADO.NET / Oracle-specific logic lives here
}
Enter fullscreen mode Exit fullscreen mode

}

The Controller depends only on IclsWorkFlow, never touching clsWorkFlow or any ADO.NET code directly:

csharp
public class WorkFlowController : ControllerBase
{
private readonly IclsWorkFlow _workFlowDataAccess;

public WorkFlowController(IclsWorkFlow workFlowDataAccess)
{
    _workFlowDataAccess = workFlowDataAccess;
}
Enter fullscreen mode Exit fullscreen mode

}

This is a Repository Pattern, even though nothing in the codebase is named "Repository." The naming convention here (cls prefix, following the project's existing style) is different from the textbook IEmployeeRepository / EmployeeRepository naming, but the structural intent is identical: hide database-specific implementation details behind a clean interface, so the rest of the application depends on a contract rather than a concrete data-access mechanism. The flow is straightforward — UI, through a thin Controller acting as the business logic layer, calling the IclsWorkFlow interface, which resolves to clsWorkFlow handling the actual ADO.NET calls.

Recognizing this mattered less for adding anything new, and more for realizing the project already followed a sound, testable structure — the pattern was present in substance, just not in naming convention.

Pattern Two: Where I Was Wrong About Template Method

A separate part of the project has a shared base class for different request types — leave requests, advance requests — each inheriting common fields and an approval method:

csharp
class BaseRequest
{
public string EmpID;
public int Status;

public void ApproveLevel(int currentSeqNo, int nextSeqNo)
{
    Status = (currentSeqNo == nextSeqNo) ? 1 : 0;
}
Enter fullscreen mode Exit fullscreen mode

}

class LeaveRequest : BaseRequest
{
public DateTime FromDate;
public DateTime ToDate;
}

My first instinct was to call this a Template Method Pattern — a base class defining a process, subclasses customizing pieces of it. On closer inspection, that label doesn't actually fit. ApproveLevel() is a single method with fixed logic, not a defined sequence of multiple steps, and LeaveRequest doesn't override or customize any part of it. This is plain Inheritance: shared fields and one shared method, reused by subclasses that add their own unrelated fields. Calling it Template Method would have been applying the label to code that doesn't actually meet the pattern's requirements.

What Would Actually Make It a Template Method Pattern

The distinguishing requirement for Template Method is a defined sequence of steps in the base class, where individual steps — not the overall order — are customized by subclasses:

csharp
abstract class BaseRequest
{
// The "template" - a fixed sequence, defined once, never overridden
public void ProcessRequest()
{
ValidateRequest();
CheckApprovalLevel();
SendNotification();
}

protected virtual void ValidateRequest()
{
    // default shared validation, subclasses may override
}

protected void CheckApprovalLevel()
{
    // shared approval logic, same for every request type
}

protected abstract void SendNotification();
// forces each subclass to implement its own version
Enter fullscreen mode Exit fullscreen mode

}

class LeaveRequest : BaseRequest
{
protected override void SendNotification()
{
// "Your leave from X to Y is approved"
}
}

class AdvanceRequest : BaseRequest
{
protected override void SendNotification()
{
// "Your advance of X is approved"
}
}

With ProcessRequest() defining the fixed sequence — validate, then check approval, then notify — and only SendNotification() varying per subclass, this genuinely qualifies. The overall algorithm's shape is locked in the base class; only specific steps are customizable. That's the actual requirement, and it's a meaningfully different structure from simply sharing a field and a method across subclasses.

Why the Distinction Is Worth Making Carefully

It would have been easy to write "we use the Template Method Pattern here" in a design document or a technical interview answer, based on surface similarity — inheritance, a shared method, subclasses. But applying a pattern name to code that doesn't structurally satisfy the pattern's actual definition creates a false sense of design sophistication, and can mislead a teammate or reviewer who takes the label at face value and expects a defined multi-step algorithm that isn't actually there. Recognizing plain Inheritance as plain Inheritance, rather than reaching for a more impressive-sounding pattern name, is a more honest and more useful skill than pattern-matching on surface features.

Takeaway

Design patterns are most useful when they accurately describe a structure already serving a real purpose, not when they're retrofitted onto code because the shape looks vaguely similar. A Repository Pattern can exist in a codebase without ever using the word "Repository," as long as the underlying structure — interface-based abstraction over data access — is genuinely there. Conversely, code that merely shares fields and a method through inheritance isn't a Template Method Pattern unless it defines an actual sequence of steps with specific points designed for customization. Being precise about which is which, even when it means concluding "this is just inheritance, not a named pattern," is a more valuable habit than confidently mislabeling code either way.

Top comments (0)