使用责任链模式代替 if/else 语句






4.12/5 (36投票s)
使用责任链模式代替 if/else 语句
引言
目前,我正在参与构建一个 Web 应用程序。该应用程序的一个功能是允许用户上传他们的照片。但系统当前仅允许上传三种图像格式:JPG
、BMP
和 PNG
,并且未来可能会支持其他格式。
每个图像文件都有一个头部,其中包含图像格式等信息。在我们的例子中,头部如下所示:
文件格式 | 标题 |
---|---|
JPG | [0xff, 0xd8] |
BMP | [0x42, 0x4D] |
PNG | [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] |
我们的图像解码服务定义如下:
public interface IImageDecodingService
{
ImageFormat DecodeImage(byte[] imageBuffer);
}
并且 ImageFormat
定义为枚举:
public enum ImageFormat
{
Unknown,
Bmp,
Png,
Jpeg
}
一个可能的实现如下:
public class ImageDecodingService : IImageDecodingService
{
private readonly byte[] _jpgHeader = { 0xff, 0xd8 };
private readonly byte[] _pngHeader = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private readonly byte[] _bmpHeader = { 0x42, 0x4D };
public ImageFormat DecodeImage(byte[] imageBuffer)
{
if (ContainsHeader(imageBuffer, _jpgHeader))
return ImageFormat.Jpeg;
if (ContainsHeader(imageBuffer, _pngHeader))
return ImageFormat.Png;
if (ContainsHeader(imageBuffer, _bmpHeader))
return ImageFormat.Bmp;
return ImageFormat.Unknown;
}
protected static bool ContainsHeader(byte[] buffer, byte[] header)
{
for (int i = 0; i < header.Length; i += 1)
{
if (header[i] != buffer[i])
{
return false;
}
}
return true;
}
}
这种方法的问题是,每当我们支持新的图像格式时,都需要修改该类。这违反了 “开闭原则”。
更好的方法是将每个解码器实现为一个类,然后将它们链接在一起(使用 “责任链模式”)。
为了实现这一点,首先我们需要实现解码器。解码器的接口如下:
public interface IImageDecoder
{
ImageFormat DecodeImage(byte[] buffer);
}
由于解码器非常相似,我们可以提取一个基类来实现公共方法,如下所示:
public abstract class BaseDecoder : IImageDecoder
{
private ImageFormat _decodingFormat;
protected BaseDecoder(ImageFormat decodingFormat)
{
_decodingFormat = decodingFormat;
}
protected abstract byte[] Header { get; }
public ImageFormat DecodeImage(byte[] buffer)
{
if(ContainsHeader(buffer, Header))
{
return _decodingFormat;
}
return ImageFormat.Unknown;
}
private static bool ContainsHeader(byte[] buffer, byte[] header)
{
for (int i = 0; i < header.Length; i += 1)
{
if (header[i] != buffer[i])
{
return false;
}
}
return true;
}
}
现在我们的解码器如下所示:
public sealed class JpegDecoder : BaseDecoder, IImageDecoder
{
public JpegDecoder() : base(ImageFormat.Jpeg)
{ }
protected override byte[] Header
{
get { return new byte[] { 0xff, 0xd8 }; }
}
}
public sealed class BmpDecoder : BaseDecoder, IImageDecoder
{
public BmpDecoder() : base(ImageFormat.Bmp)
{ }
protected override byte[] Header
{
get { return new byte[] { 0xff, 0xd8 }; }
}
}
public sealed class PngDecoder : BaseDecoder, IImageDecoder
{
public PngDecoder() : base(ImageFormat.Png)
{ }
protected override byte[] Header
{
get { return new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; }
}
}
最后一个解码器只是返回 ImageFormat.Unknown
的解码器。实现如下:
public class UnknownImageDecoder : IImageDecoder
{
public ImageFormat DecodeImage(byte[] buffer)
{
return ImageFormat.Unknown
}
}
下一步是重构我们的基类,使其允许链式调用。重构后的类如下所示:
public abstract class BaseDecoder : IImageDecoder
{
private readonly ImageFormat _decodingFormat;
private IImageDecoder _nextChain;
protected BaseDecoder(ImageFormat decodingFormat)
{
_decodingFormat = decodingFormat;
}
protected BaseDecoder(IImageDecoder nextChain, ImageFormat decodingFormat) : this(decodingFormat)
{
if (nextChain == null)
{
throw new ArgumentNullException("nextChain");
}
_nextChain = nextChain;
}
protected abstract byte[] Header { get; }
public ImageFormat DecodeImage(byte[] buffer)
{
if (ContainsHeader(buffer, Header))
{
return _decodingFormat;
}
if (_nextChain != null)
{
return _nextChain.DecodeImage(buffer);
}
return ImageFormat.Unknown;
}
private static bool ContainsHeader(byte[] buffer, byte[] header)
{
for (int i = 0; i < header.Length; i += 1)
{
if (header[i] != buffer[i])
{
return false;
}
}
return true;
}
}
如你所见,现在我们有两个构造函数,一个接受 ImageFormat
,另一个接受 IImageDecoder
作为下一个链条和 ImageFormat
。 两个构造函数的原因是,第一个构造函数(只有一个参数)允许解码器独立使用,而第二个构造函数(有两个参数)则可以构建链条。
请注意 DecodeImage(...)
方法。现在,如果此方法不知道如何解码图像,并且指定了下一个链条,它会将责任传递给下一个链条。
我们还需要将第二个构造函数添加到我们的解码器中:
public sealed class BmpDecoder : BaseDecoder
{
public BmpDecoder()
: base(ImageFormat.Bmp)
{ }
public BmpDecoder(IImageDecoder nextChain)
: base(nextChain, ImageFormat.Bmp)
{ }
protected override byte[] Header
{
get { return new byte[] { 0xff, 0xd8 }; }
}
}
public sealed class PngDecoder : BaseDecoder
{
public PngDecoder()
: base(ImageFormat.Png)
{ }
public PngDecoder(IImageDecoder nextChain)
: base(nextChain, ImageFormat.Png)
{ }
protected override byte[] Header
{
get { return new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; }
}
}
public sealed class JpegDecoder : BaseDecoder
{
public JpegDecoder()
: base(ImageFormat.Jpeg)
{ }
public JpegDecoder(IImageDecoder nextChain)
: base(nextChain, ImageFormat.Jpeg)
{ }
protected override byte[] Header
{
get { return new byte[] { 0xff, 0xd8 }; }
}
}
为了构建链条,我们需要一个工厂来构建它并返回第一个解码器。工厂的接口如下:
public interface IImageDecoderFactory
{
IImageDecoder Create();
}
实现如下:
public class ImageDecoderFactory : IImageDecoderFactory
{
public IImageDecoder Create()
{
return new BmpDecoder(new JpegDecoder(new PngDecoder(new UnknownImageDecoder())));
}
}
现在我们的 ImageDecodingService
如下所示:
public class ImageDecodingService : IImageDecodingService
{
private readonly IImageDecoderFactory _imageDecoderFactory;
public ImageDecodingService(IImageDecoderFactory imageDecoderFactory)
{
_imageDecoderFactory = imageDecoderFactory;
}
public ImageFormat DecodeImage(byte[] imageBuffer)
{
var decoder = _imageDecoderFactory.Create();
return decoder.DecodeImage(imageBuffer);
}
}
因此,如果我们需要支持另一种格式,我们将为它实现解码器,然后将其添加到工厂中。在实际应用中,你将使用 DI 容器 注册解码器,然后 DI 容器会将解码器传递给工厂,工厂将它们链接在一起。 这样,你就不需要更改任何现有代码来支持另一种格式。