在这篇文章中,我们使用c#来看看Template Method 这个设计模式,以及它如何帮助开发人员解决一些有趣的问题。
Template Method这种设计模式属于行为设计模式,顾名思义,它定义了模板,可以进一步使用它来创建一些东西。你可以把它想象成模板,你只需要选择颜色和涂漆然后就可以毫不费力地在墙上或其他表面上创造设计。
让我们以一个例子来解释。我们将实现一个日志记录器,它可以在多个地方登录,比如数据库、文件或在电子邮件中发送日志。我们将从一个简单的解决方案开始,并逐步重构它,看看使用模板方法模式对我们有什么帮助。
示例是用c#编写的,但是对于了解基本的OOPS概念的人来说,代码很容易理解。
我们为每种类型的记录器都有三个类,即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.");
}
}
没有代码可重用性——SerializeMessage()在每个类中都有相同的实现。
我们已经创建了类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.");
}
}
所有的记录器现在都有三种操作:打开连接/文件、写入日志消息和关闭/销毁文件/对象/连接。
所以我们可以假设一个典型的日志记录器总是会有这样的操作,但是,一个在以后实现一些新的日志记录器的人必须记住并实现这些操作。不应该是强制性的吗?
Log()记录日志没有什么需要实现的特别逻辑,只是按顺序调用所有其他的方法,不是吗?
我们在抽象类中添加了抽象方法,其中有一个通用名字,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.");
}
}
在这里,我们的算法/程序的所有步骤都将被执行,但是我有一些可选的步骤,用户可以根据需要选择是否调用。
假设在我们的示例中,随着登录到数据存储,我可以选择让用户选择是否登录到控制台。为了实现这一点,我添加了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中,您可以看到代码看起来更好。当您看到代码/算法使用相同的步骤时,例如给定的示例这样的小型可配置更改,模板方法可能是有用的。