C#设计模式Design Pattern示例之模板方法Template Method

在这篇文章中,我们使用c#来看看Template Method 这个设计模式,以及它如何帮助开发人员解决一些有趣的问题。

Template Method这种设计模式属于行为设计模式,顾名思义,它定义了模板,可以进一步使用它来创建一些东西。你可以把它想象成模板,你只需要选择颜色和涂漆然后就可以毫不费力地在墙上或其他表面上创造设计。

让我们以一个例子来解释。我们将实现一个日志记录器,它可以在多个地方登录,比如数据库、文件或在电子邮件中发送日志。我们将从一个简单的解决方案开始,并逐步重构它,看看使用模板方法模式对我们有什么帮助。

示例是用c#编写的,但是对于了解基本的OOPS概念的人来说,代码很容易理解。

方法1:为每种类型的日志记录器创建不同的类

我们为每种类型的记录器都有三个类,即FileLogger、EmailLogger和DatabaseLogger。它们都实现了自己的逻辑。

public class EmailLogger

{

public void Log(object message) {

string messageToLog = SerializeMessage(message);

ConnectToMailServer();

SendLogToEmail(messageToLog);

DisposeConnection();

}

private string SerializeMessage(object message) {

WriteLine("Serializing message");

return message.ToString();

}

private void ConnectToMailServer() {

WriteLine("Connecting to mail server and logging in");

}

private void SendLogToEmail(string message) {

WriteLine("Sending Email with Log Message : " + message);

}

private void DisposeConnection() {

WriteLine("Dispose Connection");

}

}

public class DatabaseLogger

{

public void Log(string message) {

string messageToLog = SerializeMessage(message);

ConnectToDatabase();

InsertLogMessageToTable(messageToLog);

CloseDbConnection();

}

private string SerializeMessage(object message) {

WriteLine("Serializing message");

return message.ToString();

}

private void ConnectToDatabase() {

WriteLine("Connecting to Database.");

}

private void InsertLogMessageToTable(string message) {

WriteLine("Inserting Log Message to DB table : " + message);

}

private void CloseDbConnection() {

WriteLine("Closing DB connection.");

}

}

class MainClass

{

static void Main(string[] args) {

FileLogger fileLogger = new FileLogger();

fileLogger.Log("Message to Log in File.");

WriteLine();

EmailLogger emailLogger = new EmailLogger();

emailLogger.Log("Message to Log via Email.");

WriteLine();

DatabaseLogger databaseLogger = new DatabaseLogger();

databaseLogger.Log("Message to Log in DB.");

}

}

评估方法1

没有代码可重用性——SerializeMessage()在每个类中都有相同的实现。

方法2:将重复代码移动到公共位置

我们已经创建了类AbstractLogger,并移动了这里的公共代码(即SerializeMessage)。所有需要此代码的类都将继承这个类以重用代码。

public abstract class AbstractLogger

{

protected string SerializeMessage(object message) {

WriteLine("Serializing message");

return message.ToString();

}

}

public class FileLogger : AbstractLogger

{

public void Log(object message) {

string messageToLog = SerializeMessage(message);

OpenFile();

WriteLogMessage(messageToLog);

CloseFile();

}

private void OpenFile() {

WriteLine("Opening File.");

}

private void WriteLogMessage(string message) {

WriteLine("Appending Log message to file : " + message);

}

private void CloseFile() {

WriteLine("Close File.");

}

}

public class EmailLogger : AbstractLogger

{

public void Log(object message) {

string messageToLog = SerializeMessage(message);

ConnectToMailServer();

SendLogToEmail(messageToLog);

DisposeConnection();

}

private void ConnectToMailServer() {

WriteLine("Connecting to mail server and logging in");

}

private void SendLogToEmail(string message) {

WriteLine("Sending Email with Log Message : " + message);

}

private void DisposeConnection() {

WriteLine("Dispose Connection");

}

}

public class DatabaseLogger : AbstractLogger

{

public void Log(string message) {

string messageToLog = SerializeMessage(message);

ConnectToDatabase();

InsertLogMessageToTable(messageToLog);

CloseDbConnection();

}

private void ConnectToDatabase() {

WriteLine("Connecting to Database.");

}

private void InsertLogMessageToTable(string message) {

WriteLine("Inserting Log Message to DB table : " + message);

}

private void CloseDbConnection() {

WriteLine("Closing DB connection.");

}

}

class MainClass

{

static void Main(string[] args) {

FileLogger fileLogger = new FileLogger();

fileLogger.Log("Message to Log in File.");

WriteLine();

EmailLogger emailLogger = new EmailLogger();

emailLogger.Log("Message to Log via Email.");

WriteLine();

DatabaseLogger databaseLogger = new DatabaseLogger();

databaseLogger.Log("Message to Log in DB.");

}

}

回顾方法2

所有的记录器现在都有三种操作:打开连接/文件、写入日志消息和关闭/销毁文件/对象/连接。

所以我们可以假设一个典型的日志记录器总是会有这样的操作,但是,一个在以后实现一些新的日志记录器的人必须记住并实现这些操作。不应该是强制性的吗?

Log()记录日志没有什么需要实现的特别逻辑,只是按顺序调用所有其他的方法,不是吗?

方法3:强制用户执行必需的步骤,并将调用这些步骤的代码转移到基类中

我们在抽象类中添加了抽象方法,其中有一个通用名字,OpenDataStoreOperation()、LogMessage()和CloseDataStoreOpreation()表示方法2问题中提到的三个操作。所以所有的Logger都必须执行它们。

它们给了我们一个更大的优势,因为我们还可以将Log()移动到一个抽象类,因为在子类中被调用的所有方法都可以在父类中使用。

上述方法的两个问题都在这个方法中得到了解决——这就是我们实现Template Method Design Pattern(模板方法设计模式)的方法。继承AbstractLogger类的任何类只需实现一些方法,在本例中,它们已经有了一些具体的方法,如SerializeMessage()。我们甚至可以在具体的方法中使用虚拟关键字来提供可选的实现。

public abstract class AbstractLogger

{

protected string SerializeMessage(object message) {

WriteLine("Serializing message");

return message.ToString();

}

protected abstract void OpenDataStoreOperation();

protected abstract void LogMessage(string messageToLog);

protected abstract void CloseDataStoreOpreation();

public void Log(object message) {

string messageToLog = SerializeMessage(message);

OpenDataStoreOperation();

LogMessage(messageToLog);

CloseDataStoreOpreation();

}

}

public class FileLogger : AbstractLogger

{

protected override void OpenDataStoreOperation() {

WriteLine("Opening File.");

}

protected override void LogMessage(string messageToLog) {

WriteLine("Appending Log message to file : " + messageToLog);

}

protected override void CloseDataStoreOpreation() {

WriteLine("Close File.");

}

}

public class EmailLogger : AbstractLogger

{

protected override void OpenDataStoreOperation() {

WriteLine("Connecting to mail server and logging in");

}

protected override void LogMessage(string messageToLog) {

WriteLine("Sending Email with Log Message : " + messageToLog);

}

protected override void CloseDataStoreOpreation() {

WriteLine("Dispose Connection");

}

}

public class DatabaseLogger : AbstractLogger

{

protected override void OpenDataStoreOperation() {

WriteLine("Connecting to Database.");

}

protected override void LogMessage(string messageToLog) {

WriteLine("Inserting Log Message to DB table : " + messageToLog);

}

protected override void CloseDataStoreOpreation() {

WriteLine("Closing DB connection.");

}

}

class MainClass

{

static void Main(string[] args) {

FileLogger fileLogger = new FileLogger();

fileLogger.Log("Message to Log in File.");

WriteLine();

EmailLogger emailLogger = new EmailLogger();

emailLogger.Log("Message to Log via Email.");

WriteLine();

DatabaseLogger databaseLogger = new DatabaseLogger();

databaseLogger.Log("Message to Log in DB.");

}

}

回顾方法3

在这里,我们的算法/程序的所有步骤都将被执行,但是我有一些可选的步骤,用户可以根据需要选择是否调用。

方法4:让调用者来决定怎么记录log

假设在我们的示例中,随着登录到数据存储,我可以选择让用户选择是否登录到控制台。为了实现这一点,我添加了tge基类中的布尔属性(ConsoleLogging)和一个虚拟方法(LogToConsole())。在Log()中,我添加了一种条件,该条件是基于ConsoleLogging的值,即是否执行LogToConsole()或不执行(参见代码)。现在,如果用户想要登录到控制台,他们只需要在ConsoleLogging的属性中传递true(参见Main() EmailLogger)。

public abstract class AbstractLogger

{

public bool ConsoleLogging { get; set; }

protected string SerializeMessage(object message) {

WriteLine("Serializing message");

return message.ToString();

}

protected abstract void OpenDataStoreOperation();

protected abstract void LogMessage(string messageToLog);

protected abstract void CloseDataStoreOpreation();

protected virtual void LogToConsole(string messageToLog) {

WriteLine("Writing in Console : " + messageToLog);

}

public void Log(object message) {

string messageToLog = SerializeMessage(message);

OpenDataStoreOperation();

LogMessage(messageToLog);

CloseDataStoreOpreation();

if (ConsoleLogging) {

LogToConsole(messageToLog);

}

}

}

public class FileLogger : AbstractLogger

{

protected override void OpenDataStoreOperation() {

WriteLine("Opening File.");

}

protected override void LogMessage(string messageToLog) {

WriteLine("Appending Log message to file : " + messageToLog);

}

protected override void CloseDataStoreOpreation() {

WriteLine("Close File.");

}

}

public class EmailLogger : AbstractLogger

{

protected override void OpenDataStoreOperation() {

WriteLine("Connecting to mail server and logging in");

}

protected override void LogMessage(string messageToLog) {

WriteLine("Sending Email with Log Message : " + messageToLog);

}

protected override void CloseDataStoreOpreation() {

WriteLine("Dispose Connection");

}

}

public class DatabaseLogger : AbstractLogger

{

protected override void OpenDataStoreOperation() {

WriteLine("Connecting to Database.");

}

protected override void LogMessage(string messageToLog) {

WriteLine("Inserting Log Message to DB table : " + messageToLog);

}

protected override void CloseDataStoreOpreation() {

WriteLine("Closing DB connection.");

}

}

class MainClass

{

static void Main(string[] args) {

FileLogger fileLogger = new FileLogger();

fileLogger.Log("Message to Log in File.");

WriteLine();

EmailLogger emailLogger = new EmailLogger();

emailLogger.ConsoleLogging = true;

emailLogger.Log("Message to Log via Email.");

WriteLine();

DatabaseLogger databaseLogger = new DatabaseLogger();

databaseLogger.Log("Message to Log in DB.");

}

}

结论

在方法4中,您可以看到代码看起来更好。当您看到代码/算法使用相同的步骤时,例如给定的示例这样的小型可配置更改,模板方法可能是有用的。

原文发布于微信公众号 - 程序你好(codinghello)

原文发表时间:2018-06-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏挖坑填坑

Asp.net+Vue2构建简单记账WebApp之二(使用ABP迅速搭建.Net后台)

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。 ASP.NET Boilerplate是一个用最佳实践...

1803
来自专栏大内老A

我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案

几个星期之前写了一篇关于如何通过WCF进行 双向通信的文章([原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communic...

2228
来自专栏菩提树下的杨过

利用FileWatcher实现文件实时监视

FileWatcher能实现对某一目录的文件(新建,改名,内容修改,删除)的实时监视 using System; using System.IO; using ...

2218
来自专栏葡萄城控件技术团队

ASP.NET Web API 应用教程(一) ——数据流使用

相信已经有很多文章来介绍ASP.Net Web API 技术,本系列文章主要介绍如何使用数据流,HTTPS,以及可扩展的Web API 方面的技术,系列文章主要...

3818
来自专栏Porschev[钟慰]的专栏

asp.net生成静态页

做个生成静态页示例: 采用替换模版页的形式生成静态页 第一步:新建项目,创建一个简单模版页:TemplatePage.htm <!DOCTYPE html PU...

2216
来自专栏.NET开发那点事

使用Microsoft Fakes进行单元测试(2)

接上一篇使用Microsoft Fakes进行单元测试(1) 下面进行Shim的演示。 2.使用Shim替换静态方法 假设我们需要一个工具方法用来格式化当前时...

2209
来自专栏恰童鞋骚年

自己动手写一个简单的MVC框架(第一版)

  路由(Route)、控制器(Controller)、行为(Action)、模型(Model)、视图(View)

1542
来自专栏有趣的django

Django rest framework(7)----分页

第一种分页  PageNumberPagination 基本使用 (1)urls.py urlpatterns = [ re_path('(?P<ve...

6417
来自专栏用户2442861的专栏

Java:HttpClient篇,HttpClient4.2在Java中的几则应用:Get、Post参数、Session(会话)保持、Proxy(代理服务器)设置,多线程设置...

新版HttpClient4.2与之前的3.x版本有了很大变化,建议从http://hc.apache.org/处以得到最新的信息。

2541
来自专栏技术小讲堂

State模式的经典应用场景:订单处理(c#实现)场景描述遇到问题解决问题走起

State模式在对象内部状态发生变化的时候,改变自身的行为,这通常是通过切换内部状态对象实现的,对象将自身在各个状态的行为推给了状态对象,从而解开了行为与对象的...

2874

扫码关注云+社区

领取腾讯云代金券