模板和策略设计模式的比较
根据我的一些工作经验,对模板和策略设计模式的比较。
引言
在本文中,我通过我工作中的代码示例演示了模板和策略设计模式之间的比较。阅读本文后,读者将了解模板方法和策略设计模式之间的异同。首先,我将描述我必须实现的软件功能的问题或需求,之后我将描述我的初始解决方案以及后来出现的该解决方案的问题。之后,我将描述如何解决我的第一个解决方案中的问题。
我最初面临的问题
我正在处理的数据处理软件的一项功能要求是将数据文件从其他几个系统转换为我们本地系统。有 4 个其他或外部系统生成一些数据,这些数据与我们的数据格式略有不同。我无法控制这些系统,因为它们是外部系统。因此,我从 4 个不同的系统获得了 4 个不同的文件,需要转换为我们自己的系统可读的文件。
我的初始解决方案
仔细分析这些外部系统文件后,我意识到它们有一些相似之处和一些差异。因此,我开发了一个通用的转换算法并使用了模板方法。
模板方法:
简而言之,模板方法设计模式在一个方法中包含通用算法,并允许子类定义该算法的一些特定步骤。通过这种方式,通用算法可以通过为每个单独的情况或案例进行子类化来应用于许多情况。
在此模式中,包含算法的类必须是抽象的;此类应使算法的某些步骤抽象化,以便子类可以重写它们。
在上面的图中,TemplateMethod()
包含算法的步骤,而 abstractoperation1()
和 asbtractionoperation2()
是算法内部的一些特定步骤,但它们将由 ConcreteClass1
实现,而不是 AbstractClass
。因此,我们可以以多种方式扩展此通用算法。
让我们讨论我基于此模板方法的初始解决方案。正如我之前讨论的,我开发了一个通用算法数据转换,其步骤如下
算法步骤
步骤 1: 从外部系统文件读取行。
步骤 2: 处理每个字符串行并将其转换为位数组(布尔数组)
步骤 2(a) 在转换为位数组之前格式化每一行
步骤 2(a)(a) 处理行中的每个元素。
步骤 3: 将每个布尔数组(位)转换为字节数组
步骤 4: 将字节数组写入文件。
我必须对每个外部系统文件重复应用此算法,以将它们转换为我的本地系统文件。为此,我需要为每个系统文件更改此通用算法,因为每个文件都与其他文件不同。
仔细分析输入文件后,我发现只有步骤 2 会为每个文件更改,而步骤 1、3 和 4 将对每个输入文件保持不变。
这看起来是应用模板方法设计模式的完美情况。我甚至深入研究了步骤 2,并将步骤 2 分解为另外两个步骤。在所有四个文件中,我只需要更改步骤 2 的这两个步骤。现在我可以将 2 个算法步骤抽象化,并允许子类实现它们。以下是我初始解决方案的类图。
其中一个子类实现如下
class DecimalFrameSyncEndDataConverter: AbstractDataConverter
{
protected override short ParseEachElementInLine(string[] strByteArray, int j)
{
short ashort = short.Parse(strByteArray[j]);
return ashort;
}
protected override void FormatEachLineBeforeProcessing(string[] strByteArray)
{
string FS1 = strByteArray[47];
string FS2 = strByteArray[48];
for (int strByteCounter = 48; strByteCounter >= 3; strByteCounter--)
{
strByteArray[strByteCounter] = strByteArray[strByteCounter - 2];
}
strByteArray[1] = FS1;
strByteArray[2] = FS2;
}
}
因此,这看起来是一个可靠的方法,因为许多代码在具体类之间共享,并且转换运行良好。
问题何时出现?当出现一种全新的文件类型时...
软件开发中有一句名言:“唯一不变的就是变化”。过了一段时间,我发现还有一个系统是客户希望整合的。该外部系统生成的输出文件与之前的文件完全不同。
经过仔细分析,我发现我需要更改算法的所有步骤,为此我需要修改抽象类。从设计的角度来看,这是一件坏事,因为类应该对修改关闭,但对扩展开放。此外,可能还有其他外部系统有一天会暴露给我,并要求开发数据转换代码。因此,我需要另一种设计类的方法。
我如何处理这个新问题?
为了解决这个问题,我使用了策略设计模式。这种模式与模板方法密切相关。但它提供了更大的灵活性。
策略模式
策略模式封装了对象的行为。策略模式还允许我们定义一组算法。每个类定义自己的算法,其步骤可能彼此完全不同。
我使用这种模式是因为每个算法可能与其他算法截然不同。这非常适合我的设计问题,因为现在我可以处理基于完全不同算法的不同数据文件进行转换。以下是实现的类图
这是策略的简单教科书图
现在我使用策略实现解决方案。这是类图

上图中其中一个类的代码
class DecimalFrameSyncEndDataConverter : IDataConverter
{
#region ILOCAdapter Members
public void ConvertData(string fileAddress)
{
// Step 1:
// Read file:
FileStream fs = new FileStream(fileAddress, FileMode.Open);
StreamReader reader = new StreamReader(fs);
List<string> allLines = new List<string>();
while (!reader.EndOfStream)
{
string aLine = reader.ReadLine();
allLines.Add(aLine);
}
reader.Close();
//Step: 2
//Processed Each String Line and Convert them to bool arrays.
List<byte> lstByteArray = new List<byte>();
// List<short> lstShortArray = new List<short>();
String[] strByteArray = new string[100];
short[] aShortArray = new short[10];
aShortArray[0] = 1;
aShortArray[1] = 2;
aShortArray[2] = 4;
aShortArray[3] = 8;
aShortArray[4] = 16;
aShortArray[5] = 32;
aShortArray[6] = 64;
aShortArray[7] = 128;
aShortArray[8] = 256;
aShortArray[9] = 512;
List<bool[]> allBitArrays = new List<bool[]>();
for (int i = 0; i < allLines.Count; i++)
{
//string aChar = " ";
string[] separatingCharacters = { " " };
strByteArray = allLines[i].Split(separatingCharacters, 100, StringSplitOptions.RemoveEmptyEntries);
// place the frame synce at the start if it is coming at END..
// right Shift the complete array...
if (strByteArray.Length >= 49)
{
string FS1 = strByteArray[47];
string FS2 = strByteArray[48];
for (int strByteCounter = 48; strByteCounter >= 3; strByteCounter--)
{
strByteArray[strByteCounter] = strByteArray[strByteCounter - 2];
}
strByteArray[1] = FS1;
strByteArray[2] = FS2;
bool[] aBitArray = new bool[480];
int aBitArrayCount = 479;
for (int j = 1; j < strByteArray.Length; j++)
{
//lstByteArray.Add(byte.Parse(strByteArray[j]));
short ashort = short.Parse(strByteArray[j]);
bool[] shortBool = new bool[10];
for (int shortCount = 0; shortCount < aShortArray.Length; shortCount++)
{
short aBoolResult = (short)(aShortArray[shortCount] & ashort);
if (Convert.ToBoolean(aBoolResult))
{
shortBool[shortCount] = true;
}
else
{
shortBool[shortCount] = false;
}
}
for (int shortBoolCount = shortBool.Length - 1; shortBoolCount >= 0; shortBoolCount--)
{
aBitArray[aBitArrayCount] = shortBool[shortBoolCount];
aBitArrayCount -= 1;
}
}
allBitArrays.Add(aBitArray);
}
}
//step 3:
//Convert Each Bool Array(Bits) to a Byte Array
byte[] aReferenceBoolByte = new byte[8];
aReferenceBoolByte[0] = 1;
aReferenceBoolByte[1] = 2;
aReferenceBoolByte[2] = 4;
aReferenceBoolByte[3] = 8;
aReferenceBoolByte[4] = 16;
aReferenceBoolByte[5] = 32;
aReferenceBoolByte[6] = 64;
aReferenceBoolByte[7] = 128;
List<byte> allBytesToWrite = new List<byte>();
for (int allBitArrayCount = 0; allBitArrayCount < allBitArrays.Count - 1; allBitArrayCount++)
{
bool[] allBitInArray = allBitArrays[allBitArrayCount];
int allBitInArrayCount = 479;
bool[] myByteBoolArray = new bool[8];
while (allBitInArrayCount >= 0)
{
for (int readCount = 7; readCount >= 0; readCount--)
{
myByteBoolArray[readCount] = allBitInArray[allBitInArrayCount];
allBitInArrayCount--;
}
byte b = 0x00;
for (int myIntCount = 0; myIntCount < 8; myIntCount++)
{
if (myByteBoolArray[myIntCount])
{
b = (byte)(b | aReferenceBoolByte[myIntCount]);
}
else
{
b = (byte)(b | 0x00);
}
}
allBytesToWrite.Add(b);
}
}
//Step 4
// Write bytes array to a file.
FileStream fStream = new FileStream(@"C:\s2\LOCTest.DAT", FileMode.Create);
BinaryWriter bWriter = new BinaryWriter(fStream);
foreach (byte b in allBytesToWrite)
{
bWriter.Write(b);
}
fStream.Close();
bWriter.Close();
// return the report..
}
#endregion
}
现在是新类 BinarDataConverter.cs 的代码
class BinaryDataConverter : IDataConverter
{
public void ConvertData(string fileAddress)
{
// find all major frames...
FileStream fs = new FileStream(fileAddress, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] buffer = new byte[br.BaseStream.Length];
br.Read(buffer, 0, buffer.Length);
br.Close();
fs.Close();
List<byte> fileData = new List<byte>();
fileData.AddRange(buffer);
short[] aShortArray = new short[10];
aShortArray[0] = 1;
aShortArray[1] = 2;
aShortArray[2] = 4;
aShortArray[3] = 8;
aShortArray[4] = 16;
aShortArray[5] = 32;
aShortArray[6] = 64;
aShortArray[7] = 128;
aShortArray[8] = 256;
aShortArray[9] = 512;
List<bool[]> allBitArrays = new List<bool[]>();
List<int> subFrameLocations = SearchConstPattern(fileData, new byte[] { 0x19, 0xFF, 0x22, 0x42 });
for (int majCount = 0; majCount < subFrameLocations.Count; majCount++)
{
byte[] aByteArray = new byte[104];
int position = subFrameLocations[majCount];
fileData.CopyTo(subFrameLocations[majCount] , aByteArray, 0, 104);
int aBitArrayCount = 479;
bool[] aBitArray = new bool[480];
// now retreive the values from each sub frame..
for (int i = 1; i < aByteArray.Length; i += 2)
{
// we want to skip extra data present in LOC format..
if (i >= 4 && i <= 11)
continue;
// bringing the data in short format (2 byte data) is same across all
// some time we bring from string to short.. but in this we have binary data
short shortValue = 0x0000;
shortValue = (short)(shortValue | aByteArray[i]);
shortValue = (short)(shortValue << 8);
shortValue = (short)(shortValue | aByteArray[i - 1]);
// convert that shortValue to bool value..
bool[] shortBool = new bool[10];
for (int shortCount = 0; shortCount < aShortArray.Length; shortCount++)
{
short aBoolResult = (short)(aShortArray[shortCount] & shortValue);
if (Convert.ToBoolean(aBoolResult))
{
shortBool[shortCount] = true;
}
else
{
shortBool[shortCount] = false;
}
}
// we are putting bool from the end .. it will be retreive from the end..
for (int shortBoolCount = shortBool.Length - 1; shortBoolCount >= 0; shortBoolCount--)
{
aBitArray[aBitArrayCount] = shortBool[shortBoolCount];
aBitArrayCount -= 1;
}
}
allBitArrays.Add(aBitArray);
}
// from bit arrays we convert the data to binary format.. this is same across all files..
byte[] aReferenceBoolByte = new byte[8];
aReferenceBoolByte[0] = 1;
aReferenceBoolByte[1] = 2;
aReferenceBoolByte[2] = 4;
aReferenceBoolByte[3] = 8;
aReferenceBoolByte[4] = 16;
aReferenceBoolByte[5] = 32;
aReferenceBoolByte[6] = 64;
aReferenceBoolByte[7] = 128;
List<byte> allBytesToWrite = new List<byte>();
for (int allBitArrayCount = 0; allBitArrayCount < allBitArrays.Count - 1; allBitArrayCount++)
{
bool[] allBitInArray = allBitArrays[allBitArrayCount];
int allBitInArrayCount = 479;
bool[] myByteBoolArray = new bool[8];
while (allBitInArrayCount >= 0)
{
for (int readCount = 7; readCount >= 0; readCount--)
{
myByteBoolArray[readCount] = allBitInArray[allBitInArrayCount];
allBitInArrayCount--;
}
byte b = 0x00;
for (int myIntCount = 0; myIntCount < 8; myIntCount++)
{
if (myByteBoolArray[myIntCount])
{
b = (byte)(b | aReferenceBoolByte[myIntCount]);
}
else
{
b = (byte)(b | 0x00);
}
}
allBytesToWrite.Add(b);
}
}
// write binary data
FileStream fStream = new FileStream(@"C:\LOCTest.DAT", FileMode.Create);
BinaryWriter bWriter = new BinaryWriter(fStream);
foreach (byte b in allBytesToWrite)
{
bWriter.Write(b);
}
fStream.Close();
bWriter.Close();
// return the report..
}
private List<int> SearchConstPattern(List<byte> ncFrame4, byte[] pattern){.. }
private int FindPattern(byte[] data, byte[] pattern){.. }
private int[] ComputeFailure(byte[] pattern){ ...}
}
正如人们所看到的,我必须在每个实现类中重复很多代码。
策略模式在此上下文中的好处
正如人们所看到的,实现类也依赖于模板方法类。如果想要更改算法的某些步骤,这种依赖会导致更改模板方法。另一方面,策略完全封装了算法。它允许实现类完全定义一个算法。因此,如果发生任何更改,则无需更改以前编写的类的代码。这是我选择策略来设计类的主要原因。
模板方法的一个特点是模板方法控制算法。这在其他情况下可能是一件好事,但在我的问题中,这限制了我设计类。另一方面,策略不控制算法的步骤,这使我能够添加完全不同的转换方法。因此,在我的情况下,策略有助于我的实现。
策略的一个缺点是代码冗余过多,代码共享较少。正如本文中提供的示例所明显,我必须在四个类中一遍又一遍地重复相同的代码。因此,维护起来很困难,因为如果我的系统的实现(例如对所有类都通用的步骤 4)发生更改,那么我将必须在所有 5 个类中更新它。另一方面,在模板方法中,我只能更改超类,并且更改会反映到子类中。因此,模板方法在类之间提供了极低的冗余量和极高的代码共享量。
策略还允许在运行时更改算法。在模板方法中,必须重新初始化对象。策略的这一特性提供了大量的灵活性。从设计的角度来看,人们必须倾向于组合而不是继承。因此,使用策略模式也成为开发的首选。
结论
没有正确或错误的设计模式。这取决于一个人面临的问题,我为什么选择策略,因为在我的情况下,由于低依赖性,它更有帮助。在本文中,我提供了功能代码的快照。此功能属于大型数据处理软件。我解释了客户的需求。我解释了我的初始解决方案的设计,然后解释了用户需求的变化,然后我解释了更新的设计。这是一个很好的例子,可以学习模板方法模式和策略模式的一些异同。
参考文献
- (1)《设计模式:可复用面向对象软件的元素》一书,作者 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。
- (2)《深入浅出设计模式》一书,作者 Elisabeth Freeman、Eric Freeman、Bert Bates、Kathy Sierra 和 Elisabeth Robson。