65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.12/5 (36投票s)

2015年11月5日

CPOL

2分钟阅读

viewsIcon

128758

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

引言

目前,我正在参与构建一个 Web 应用程序。该应用程序的一个功能是允许用户上传他们的照片。但系统当前仅允许上传三种图像格式:JPGBMPPNG,并且未来可能会支持其他格式。

每个图像文件都有一个头部,其中包含图像格式等信息。在我们的例子中,头部如下所示:

文件格式 标题
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 容器会将解码器传递给工厂,工厂将它们链接在一起。 这样,你就不需要更改任何现有代码来支持另一种格式。

© . All rights reserved.