Chain of Responsibility Design Pattern در .NET C#

آخرین بروز رسانی: 1400/11/09

تعریف الگوی طراحی زنجیره مسئولیت

الگوی طراحی زنجیره مسئولیت یکی از الگوهای طراحی رفتاری (behavioral ) است.

این اجازه می دهد تا یک درخواست یا یک شی را از طریق زنجیره ای از کنترل کننده ها عبور دهید.

هنگامی که یک کنترل کننده درخواستی را دریافت می کند، تصمیم می گیرد که آیا می تواند درخواست را پردازش کند یا آن را به کنترل کننده بعدی در زنجیره ارسال کند.

اگر قبلاً تجربه ای با توسعه وب دارید، احتمالاً چیزی به نام Middleware شنیده اید. کل سیستم مدیریت قطعات Middleware از الگوی طراحی زنجیره مسئولیت استفاده می کند.

 

مزایای الگوی طراحی زنجیره مسئولیت

خوب، فرض کنید سیستمی دارید که در آن باید یک سری مراحل را روی یک شی یا یک درخواست ورودی اعمال کنید. ممکن است فکر کنید همه این مراحل را در یک ماژول اعمال کنید، مانند داشتن یک متد عظیم که شی یا درخواست را در تمام مراحل یک به یک اجرا می کند و در نهایت نتیجه را برمی گرداند.

بله، کار می کند، با این حال، می دانید که در نرم افزار فقط داشتن کد کار نیست، داشتن یک کد قابل نگهداری و توسعه پذیر نیز هست.

اگر از این رویکرد پیروی کنید، در نهایت با یک متد یا ماژول بزرگ مواجه خواهید شد که کارهای زیادی را انجام می دهد. علاوه بر این، هر بار که نیاز به گسترش مراحل داشته باشید، باید دوباره به این ماژول یا متد مراجعه کرده و تغییرات بیشتری را اعمال کنید. همه اینها در نهایت به یک سیستم شکننده ختم می شود.

با این حال، اگر الگوی طراحی زنجیره مسئولیت را پیاده سازی کنید، در نهایت با یک سیستم ماژولار مواجه خواهید شد که در آن کنترل هر ماژول را دارید که در حال حاضر حداقل ممکن را انجام می دهد.

ممکن است کسی استدلال کند که حتی اگر این مراحل را به ماژول‌های کوچک‌تر تقسیم کنیم، ماژول اصلی در نهایت از همه این ماژول‌های کوچک‌تر استفاده کرده و اجرا می‌کند، بنابراین در پایان همینطور خواهد بود.

قطعا نه. بله، ماژول اصلی از همه ماژول های کوچکتر استفاده می کند و اجرا می کند، زیرا در پایان این همان کاری است که ما باید به آن دست یابیم، با این حال، این نیست که آیا منطقی اجرا می شود یا نه، بلکه این است که کدام ماژول مسئول اجرای کدام منطق است.

تقسیم قوانین و منطق کل تجارت بزرگ به ماژول های کوچکتر, به کنترل، امکان نگهداری، گسترش، آزمایش و …  کمک میکند

 

الگوی طراحی زنجیره مسئولیت چگونه کار می کند؟

بیایید فرض کنیم که می‌خواهید درخواست شما توسط کنترل‌کننده‌ها به ترتیب پردازش شود، اما اگر هر یک از آنها شکست خورد، می‌خواهید زنجیره را متوقف کنید و یک پاسخ توصیفی فوری دریافت کنید.

در این حالت نمودار به صورت زیر خواهد بود:

 

و فرآیند به شرح زیر خواهد بود:
1.  درخواست به اولین کنترل کننده ارسال می شود.
2 . کنترل کننده تأیید می کند که آیا قادر به پردازش درخواست است یا خیر. اگر بله، درخواست را پردازش می کند و آن را به کنترل کننده بعدی ارسال می کند. اگر نه، با برخی از پیام های توصیفی یا پاسخ به تماس گیرنده با شکست مواجه می شود.

 

با این حال، این تنها راه انجام آن نیست. ممکن است نیازهای شما کمی متفاوت باشد که فقط باید پس از اولین کنترل کننده موفق هر ترتیبی که در زنجیره باشد، پاسخ دهید.

در این حالت نمودار به صورت زیر خواهد بود:

و فرآیند به شرح زیر خواهد بود:
1.  درخواست به اولین کنترل کننده ارسال می شود.
2.  کنترل کننده تأیید می کند که آیا قادر به پردازش درخواست است یا خیر. اگر بله، درخواست را پردازش می کند و با پاسخ موفقیت آمیز به تماس گیرنده برمی گردد. اگر نه، درخواست را به دست‌دهنده بعدی ارسال می‌کند.

 

با این حال، حتی در صورت داشتن روش‌های مختلف کار، نمودار کلاسی یکسانی خواهیم داشت که تفاوت‌ها در پیاده‌سازی‌ها است، نه در سطح انتزاع‌ها (مفاهیم).

با این اوصاف، بیایید اکنون با استفاده از یک مثال ساده به اجرای الگوی طراحی زنجیره مسئولیت بپردازیم.

 

پیاده سازی الگوی طراحی زنجیره مسئولیت

namespace ChainOfResponsibilityImplementation.Abstractions
{
    public interface IHandler
    {
        void SetNextHandler(IHandler next);
        IResponse Handle(IRequest request);
    }
}
using System.Collections.Generic;

namespace ChainOfResponsibilityImplementation.Abstractions
{
    public interface IRequest
    {
        int Id { get; }
        IReadOnlyCollection Messages { get; }
    }
}
using System.Collections.Generic;

namespace ChainOfResponsibilityImplementation.Abstractions
{
    public interface IResponse
    {
        IReadOnlyCollection Messages { get; }
    }
}
using System.Collections.Generic;
using ChainOfResponsibilityImplementation.Abstractions;

namespace ChainOfResponsibilityImplementation.Implementations
{
    public class Request : IRequest
    {
        public int Id { get; }
        public IReadOnlyCollection Messages { get; }

        public Request(int id, IReadOnlyCollection messages)
        {
            Id = id;
            Messages = messages;
        }
    }
}
using System.Collections.Generic;
using ChainOfResponsibilityImplementation.Abstractions;

namespace ChainOfResponsibilityImplementation.Implementations
{
    public class Response : IResponse
    {
        public IReadOnlyCollection Messages { get; }

        public Response(IReadOnlyCollection messages)
        {
            Messages = messages;
        }
    }
}
using ChainOfResponsibilityImplementation.Abstractions;

namespace ChainOfResponsibilityImplementation.Implementations.Handlers
{
    public abstract class HandlerBase : IHandler
    {
        protected IHandler NextHandler;

        public void SetNextHandler(IHandler next)
        {
            NextHandler = next;
        }

        public abstract IResponse Handle(IRequest request);
    }
}
using System.Collections.Generic;
using ChainOfResponsibilityImplementation.Abstractions;

namespace ChainOfResponsibilityImplementation.Implementations.Handlers
{
    public class Handler1 : HandlerBase
    {
        public override IResponse Handle(IRequest request)
        {
            var newMessages = new List(request.Messages) { "Handled by Handler 1" };

            if (NextHandler != null)
            {
                var newRequest = new Request(request.Id, newMessages);
                return NextHandler.Handle(newRequest);
            }

            return new Response(newMessages);
        }
    }
}
Handler2//
Same as Handler1 with replacing the message “Handled by Handler 1” with “Handled by Handler 2”.//
using System;
using System.Collections.Generic;
using System.Linq;
using ChainOfResponsibilityImplementation.Abstractions;

namespace ChainOfResponsibilityImplementation.Implementations.Handlers
{
    public class AggregateHandler : HandlerBase
    {
        private readonly List m_Handlers;

        public AggregateHandler(params IHandler[] handlers)
        {
            if (handlers == null || !handlers.Any())
            {
                throw new ArgumentNullException(nameof(handlers), "No handlers are set.");
            }

            m_Handlers = new List();
            m_Handlers.AddRange(handlers);
        }

        public override IResponse Handle(IRequest request)
        {
            if (m_Handlers.Count > 1)
            {
                for (var handlerIndex = 0; handlerIndex < m_Handlers.Count - 1; handlerIndex++)
                {
                    m_Handlers[handlerIndex].SetNextHandler(m_Handlers[handlerIndex + 1]);
                }
            }

            return m_Handlers[0].Handle(request);
        }
    }
}
using System;
using System.Collections.Generic;
using ChainOfResponsibilityImplementation.Implementations;
using ChainOfResponsibilityImplementation.Implementations.Handlers;

namespace ChainOfResponsibilityImplementation
{
    public class Program
    {
        static void Main(string[] args)
        {
            var initialRequest = new Request(1, new List { "This is the initial request" });

            var aggregateHandler1 = new AggregateHandler(
                new Handler1(),
                new Handler2());

            Console.WriteLine(string.Join("\n\r", aggregateHandler1.Handle(initialRequest).Messages));

            var aggregateHandler2 = new AggregateHandler(
                new Handler2(),
                new Handler1());

            Console.WriteLine();

            Console.WriteLine(string.Join("\n\r", aggregateHandler2.Handle(initialRequest).Messages));

            Console.ReadLine();
        }
    }
}

 

 

😱 نگرانی من در مورد الگوی طراحی زنجیره مسئولیت

اگر به خاطر داشته باشید، قبلاً در بخش قبلی این مقاله به شما گفته بودم که یک نگرانی دارم. نگرانی من این است که به دنبال این طراحی، بیش از یک مشکل داریم.


:از جایی که من می بینم، مشکلات زیر را خواهیم داشت

1.  گرداننده ها کارهای زیادی انجام می دهند.
2.  handlerها نه تنها پیاده سازی های خود را ارائه می دهند، بلکه کل دنباله را نیز کنترل می کنند.
3.  هر Handler می تواند تصمیم بگیرد که کارها را به روشی متفاوت انجام دهد.
4.  ماژول اصلی در مورد آنچه در هاپ بین گرداننده ها اتفاق می افتد کاملا بی اطلاع است.
5.  اگر یک handler خاص تصمیم بگیرد که با handler بعدی تماس نگیرد چه؟ ممکن است به دلیل اشکال در کدنویسی باشد؟ یا هر اشتباه دیگر؟

 

علاوه بر این، بیایید تصور کنیم که کنترل‌کننده‌ها در واقع پلاگین‌های شخص ثالثی هستند که کاربر نهایی می‌تواند هر طور که می‌خواهد اضافه و حذف کند. در این صورت، واگذاری کنترل کامل در دست پلاگین ها خوب نخواهد بود.

بنابراین، من در واقع ترجیح می‌دهم برای رفع این مشکلات تغییراتی در طراحی اعمال کنم.

با این حال، من مطمئن نیستم که آیا پس از اعمال این تغییرات، هنوز بتوانیم آن را الگوی طراحی زنجیره مسئولیت بنامیم یا خیر.😔

 

namespace BetterChainOfResponsibilityImplementation.Abstractions
{
    public interface IHandler
    {
        IResponse Handle(IRequest originalRequest, IResponse accumulatedResponse = null);
    }
}
using BetterChainOfResponsibilityImplementation.Abstractions;

namespace BetterChainOfResponsibilityImplementation.Implementations.Handlers
{
    public abstract class HandlerBase : IHandler
    {
        public abstract IResponse Handle(IRequest originalRequest, IResponse accumulatedResponse = null);
    }
}
using System.Collections.Generic;
using BetterChainOfResponsibilityImplementation.Abstractions;

namespace BetterChainOfResponsibilityImplementation.Implementations.Handlers
{
    public class Handler1 : HandlerBase
    {
        public override IResponse Handle(IRequest originalRequest, IResponse accumulatedResponse = null)
        {
            List<string> newMessages;

            if (accumulatedResponse != null)
            {
                newMessages = new List<string>(accumulatedResponse.Messages) { "Handled by Handler 1" };
            }
            else
            {
                newMessages = new List<string>(originalRequest.Messages) { "Handled by Handler 1" };
            }

            return new Response(newMessages);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using BetterChainOfResponsibilityImplementation.Abstractions;

namespace BetterChainOfResponsibilityImplementation.Implementations.Handlers
{
    public class AggregateHandler : HandlerBase
    {
        private readonly List<IHandler> m_Handlers;

        public AggregateHandler(params IHandler[] handlers)
        {
            if (handlers == null || !handlers.Any())
            {
                throw new ArgumentNullException(nameof(handlers), "No handlers are set.");
            }

            m_Handlers = new List<IHandler>();
            m_Handlers.AddRange(handlers);
        }

        public override IResponse Handle(IRequest originalRequest, IResponse accumulatedResponse = null)
        {
            var previousResponse = m_Handlers[0].Handle(originalRequest, accumulatedResponse);

            if (m_Handlers.Count > 1)
            {
                for (var handlerIndex = 1; handlerIndex < m_Handlers.Count; handlerIndex++)
                {
                    previousResponse = m_Handlers[handlerIndex].Handle(originalRequest, previousResponse);
                }
            }

            return previousResponse;
        }
    }
}

نظر دهید

آدرس ایمیل شما منتشر نخواهد شد. فیلدهای الزامی علامت گذاری شده اند *