الگوی طراحی استراتژی (Strategy) در زبان C#

توضیح دوات: برای راهنمایی و اطلاعات بیشتر در مورد نمودار کلاس به این مقاله مراجعه کنید.

الگوی طراحی Strategy (استراتژی) یک الگوی رفتاری است که به ما اجازه می‌دهد یک خانواده از الگوریتم‌ها را تعریف کنیم، هرکدام را در یک کلاس جداگانه قرار دهیم و اشیای آنها را قابل جایگزینی کنیم.

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

ایده اصلی این الگو این است که اگر رفتار را به‌عنوان شیء محصور کنیم، می‌توانیم انتخاب کنیم که کدام شیء را استفاده کنیم و به این ترتیب، کدام رفتار را بر اساس ورودی‌ها یا حالات خارجی پیاده‌سازی کنیم. به علاوه، این امکان را فراهم می‌کنیم که رفتارهای مختلف زیادی پیاده‌سازی شوند بدون نیاز به ایجاد زنجیره‌های بزرگ از دستورات if/then یا switch.

می‌توانید کد نمونه این پست را در GitHub پیدا کنید.

مفهوم‌سازی مسئله

فرض کنید که یک اپلیکیشن مسیریابی برای مسافران داریم. این اپلیکیشن بر پایه یک نقشه ساخته شده است که به کاربران کمک می‌کند به سرعت در هر شهری جهت‌یابی کنند.

یکی از ویژگی‌هایی که بیشترین درخواست را داشت، برنامه‌ریزی خودکار مسیر بود. کاربر باید بتواند یک آدرس وارد کند و اپلیکیشن سریع‌ترین مسیر به آن مقصد را محاسبه کرده و دستورالعمل‌ها را روی نقشه نمایش دهد.

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

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

در حالی که از منظر تجاری، اپلیکیشن موفقیت بزرگی بود، بخش فنی به “پادشاهی بزرگ اسپگتی‌لند” تبدیل شد.

هر تغییری در یکی از الگوریتم‌های مسیریابی، چه رفع یک اشکال ساده یا تنظیم اندکی در امتیاز خیابان، بر کل کلاس تأثیر می‌گذاشت و احتمال ناپایداری اپلیکیشن را افزایش می‌داد.

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

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

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

Context مسئول انتخاب الگوریتم مناسب برای کار نیست. بلکه، مشتری استراتژی مورد نظر را به Context می‌دهد. در واقع، Context از نحوه عملکرد یک استراتژی آگاه نیست. می‌تواند با همه استراتژی‌ها از طریق یک واسط عمومی کار کند که فقط یک متد برای فعال‌سازی الگوریتم محصور درون ارائه می‌دهد.

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

مثال الگوی طراحی Strategy

نمودار کلاس مثلا الگوی طراحی استراتژی

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

با این‌که ممکن است هر کلاس مسیریابی با همان آرگومان‌ها مسیر متفاوتی بسازد، کلاس اصلی مسیریاب واقعاً اهمیت نمی‌دهد کدام الگوریتم انتخاب شده است، زیرا وظیفه اصلی آن نمایش مجموعه‌ای از نقاط ایست بر روی نقشه است. این کلاس یک متد برای تغییر استراتژی مسیریابی فعال دارد، بنابراین مشتریان آن، مانند رابط کاربری، می‌توانند رفتار مسیریابی انتخاب شده را با دیگری جایگزین کنند.

ساختار الگوی طراحی استراتژی

در پیاده‌سازی پایه‌ای خود، الگوی استراتژی ۴ شرکت‌کننده دارد:

  • Context: Context مرجعی به یکی از استراتژی‌های مشخص نگه می‌دارد و فقط از طریق واسط استراتژی با این شیء ارتباط برقرار می‌کند. Context هر زمان که نیاز باشد الگوریتم اجرا شود، متد اجرایی را روی شیء استراتژی مرتبط فراخوانی می‌کند. Context نمی‌داند با چه نوع استراتژی کار می‌کند یا الگوریتم چگونه اجرا می‌شود.
  • Strategy: واسط استراتژی برای همه استراتژی‌های مشخص مشترک است. این واسط متدی را تعریف می‌کند که Context برای اجرای یک استراتژی از آن استفاده می‌کند.
  • Concrete Strategy: استراتژی مشخص، یک نسخه از الگوریتمی را که Context استفاده می‌کند، پیاده‌سازی می‌کند.
  • Client: مشتری یک شیء استراتژی مشخص ایجاد می‌کند و آن را به Context می‌دهد. Context یک setter در دسترس قرار می‌دهد که به مشتریان اجازه می‌دهد استراتژی مرتبط با Context را در زمان اجرا جایگزین کنند.
نمودار کلاس الگوی طراحی استراتژی

نمودار کلاس الگوی طراحی Strategy

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

استراتژی‌های مشخص نه تنها پرداخت واقعی را انجام می‌دهند، بلکه رفتار مرحله پرداخت را نیز تغییر می‌دهند و فیلدهای مناسب برای ثبت جزئیات پرداخت را ارائه می‌کنند.

ابتدا، واسط مشترک IStrategy را ایجاد می‌کنیم:

				
					namespace Strategy.Strategies;  

/// <summary>  
/// Common interface for all strategies  
/// </summary>  
public interface IPaymentStrategy  
{  
    bool Pay(int amount);  
    void Authenticate();  
}

				
			

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

				
					using System.Text.RegularExpressions;  
using Spectre.Console;  

namespace Strategy.Strategies;  

public class CreditCardPayment : IPaymentStrategy  
{  
    private bool _isValidated;  

    public bool Pay(int amount)  
    {  
        if (!_isValidated)  
            return false;  

        AnsiConsole.WriteLine($"[green]Paying {amount} using Credit Card.[/]");  
        return true;  
    }  

    public void Authenticate()  
    {  
        var creditCardNumber = AnsiConsole.Prompt(  
            new TextPrompt<string>("Enter your card number: ")  
                .ValidationErrorMessage("[red]Please enter a valid card number[/]")  
                .Validate(e =>  
                {  
                    return Regex.IsMatch(e, @"\b\d{4}(| |-)\d{4}\1\d{4}\1\d{4}\b",  
                            RegexOptions.IgnoreCase) switch  
                        {  
                            false => ValidationResult.Error("[red]Provide a valid card number[/]"),  
                            _ => ValidationResult.Success()  
                        };  
                }));  

        var creditCardExpiration = AnsiConsole.Prompt(  
            new TextPrompt<string>("Enter your card expiration date: ")  
                .ValidationErrorMessage("[red]Please enter a valid date[/]")  
                .Validate(e =>  
                {  
                    return Regex.IsMatch(e, @"\d{2}/\d{4}",  
                            RegexOptions.IgnoreCase) switch  
                        {  
                            false => ValidationResult.Error("[red]Please enter a valid date[/]"),  
                            _ => ValidationResult.Success()  
                        };  
                }));  

        var creditCardCVV = AnsiConsole.Prompt(  
            new TextPrompt<string>("Enter your card verification value: ")  
                .ValidationErrorMessage("[red]Please enter a valid card verification value[/]")  
                .Validate(e =>  
                {  
                    return Regex.IsMatch(e, @"\d{3}",  
                            RegexOptions.IgnoreCase) switch  
                        {  
                            false => ValidationResult.Error("[red]Please enter a valid card verification value[/]"),  
                            _ => ValidationResult.Success()  
                        };  
                }));  

        AnsiConsole.WriteLine($"Card Number: [cyan]{creditCardNumber}[/]");  
        AnsiConsole.WriteLine($"Card Expiration Date: [cyan]{creditCardExpiration}[/]");  
        AnsiConsole.WriteLine($"Card CVV: [cyan]{creditCardCVV}[/]");  

        // Validate the card...  
        _isValidated = true;  
    }  
}

				
			

استراتژی مشخص دوم پرداخت با استفاده از Skrill، یک ارائه‌دهنده خدمات پرداخت، را پیاده‌سازی می‌کند. باز هم، بررسی‌ها فقط ابتدایی هستند:

				
					using System.Text.RegularExpressions;  
using Spectre.Console;  

namespace Strategy.Strategies;  

/// <summary>  
/// Concrete Strategy. Implements the Skrill payment method.  
/// </summary>  
public class SkrillPayment : IPaymentStrategy  
{  
    private static readonly Dictionary<string, string> Database = new();  
    private string _email;  
    private string _password;  
    private bool _isSignedIn;  

    public SkrillPayment()  
    {  
        Database.Add("user@example.com", "123456");  
        _email = "";  
        _password = "";  
    }  

    public bool Pay(int amount)  
    {  
        if (!_isSignedIn)   
return false;  

        AnsiConsole.WriteLine($"[green]Paying {amount} using PayPal.[/]");  
        return true;  
    }  

    /// <summary>  
    /// Authenticates the user.    
    /// </summary>    
    public void Authenticate()  
    {  
        while (!_isSignedIn)  
        {  
            _email = AnsiConsole.Prompt(  
                new TextPrompt<string>("Enter your email:")  
                    .ValidationErrorMessage("[red]Please enter a valid email[/]")  
                    .Validate(e =>  
                    {  
                        return Regex.IsMatch(e, @"^[^@\s]+@[^@\s]+\.(com|net|org|gov)$",  
                                RegexOptions.IgnoreCase) switch  
                            {  
                                false => ValidationResult.Error("[red]Provide a valid email address[/]"),  
                                _ => ValidationResult.Success()  
                            };  
                    }));  

            _password = AnsiConsole.Prompt(  
                new TextPrompt<string>("Enter your password: ")  
                    .PromptStyle("red")  
                    .Secret());  

            AnsiConsole.WriteLine(Verify()  
                ? $"[green]Data verification is successful. Welcome {_email}[/]"  
                : "[red]Wrong username or password.[/]");  
        }  
    }  

    private bool Verify()  
    {  
        if (Database.ContainsKey(_email))   
_isSignedIn = _password.Equals(Database[_email]);  
        return _isSignedIn;  
    }  
}

				
			

توجه داشته باشید که اگرچه این دو استراتژی یک واسط یکسان را پیاده‌سازی می‌کنند، اما پرداخت‌ها را به روش‌های کاملاً متفاوتی مدیریت می‌کنند. در حالی که CardPaymentStrategy فیلدهای مختلف یک کارت اعتباری را اعتبارسنجی می‌کند، SkrillPaymentStrategy یک مکانیزم ورود به سیستم را پیاده‌سازی می‌کند.

برای تکمیل اپلیکیشن نمونه، به یک شرکت‌کننده Context نیاز داریم که مرجعی به هم کالاهایی که می‌خواهیم خریداری کنیم و هم استراتژی‌ای که برای انجام این کار استفاده می‌کنیم نگه دارد. این شرکت‌کننده در کلاس Order خواهد بود:

				
					using Strategy.Strategies;  

namespace Strategy.Orders;  

/// <summary>  
/// Order class. Doesn't know the concrete payment method (strategy) the user has  
/// picked. It uses a common strategy interface to delegate collecting payment data  
/// to strategy object. It can be used to save the order to the database.  
/// </summary>  
public class Order  
{  
    private int _totalCost = 0;  
    private bool _isClosed = false;  

    public void ProcessOrder(IPaymentStrategy strategy)  
    {  
        strategy.Authenticate();  
    }  

    public void AddCost(int cost)  
    {  
        _totalCost += cost;  
    }  

    public int TotalCost => _totalCost;  

    public void Close() => _isClosed = true;  

    public bool IsClosed() => _isClosed;  
}

				
			

در نهایت، می‌توانیم به کاربر اجازه دهیم تا مواردی که می‌خواهد خریداری کند و استراتژی پرداختی که می‌خواهد استفاده کند را در کلاس Program انتخاب کند:

				
					using Spectre.Console;  
using Strategy.Orders;  
using Strategy.Strategies;  

public class Program  
{  
    private static Dictionary<string, int> _products;  
    private static Order order;  
    private static IPaymentStrategy strategy;  

    private static void Initialize()  
    {  
        _products = new Dictionary<string, int>  
            { { "Motherboard", 220 }, { "CPU", 180 }, { "HDD", 60 }, { "RAM", 120 } };  
        order = new Order();  
    }  

    public static void Main()  
    {  
        Initialize();  

        while (!order.IsClosed())  
        {  
            do  
            {  
                var products = AnsiConsole.Prompt(  
                    new MultiSelectionPrompt<string>()  
                        .Title("What [green]products[/] do you want to add to the cart?")  
                        .Required()  
                        .PageSize(10)  
                        .MoreChoicesText("[grey](Move up and down to reveal more products)[/]")  
                        .InstructionsText(  
                            "[grey](Press [blue]<space>[/] to toggle a product, " +  
                            "[green]<enter>[/] to accept)[/]")  
                        .AddChoices(new[]  
                        {  
                            "Motherboard", "CPU", "HDD", "RAM",  
                        }));  

                foreach (string product in products)  
                {  
                    order.AddCost(_products[product]);  
                }  
            } while (!AnsiConsole.Confirm("Go to payment?"));  

            order.Close();  

            if (strategy == null)  
            {  
                var paymentMethod = AnsiConsole.Prompt(  
                    new SelectionPrompt<string>()  
                        .Title("Select payment method:")  
                        .PageSize(5)  
                        .MoreChoicesText("[grey](Move up and down to reveal more payment methods)[/]")  
                        .AddChoices(new[]  
                        {  
                            "Skrill",  
                            "Credit Card"  
                        })  
                );  

                strategy = paymentMethod.Equals("Skrill") ? new SkrillPayment() : new CreditCardPayment();  
                order.ProcessOrder(strategy);  
            }  
        }  
    }  
}

				
			

اگر برنامه را اجرا کنیم، ابتدا صفحه انتخاب اقلام را می‌بینیم و برنامه از ما می‌خواهد که تعدادی محصول به سبد خرید خود اضافه کنیم:

 

پس از انتخاب چند مورد، برنامه از ما می‌خواهد که یک روش پرداخت انتخاب کنیم:

اگر پرداخت Skrill را انتخاب کنیم، برنامه از ما می‌خواهد که نام کاربری و رمز عبور خود را اعتبارسنجی کنیم:

اگر پرداخت با کارت را انتخاب کنیم، برنامه از ما می‌خواهد که اطلاعات کارت اعتباری خود را اعتبارسنجی کنیم:

مزایا و معایب الگوی استراتژی

مزایا

  • می‌توانیم الگوریتم‌های مورد استفاده در یک شیء را در زمان اجرا تعویض کنیم.
  • می‌توانیم جزئیات پیاده‌سازی یک الگوریتم را از کدی که از آن استفاده می‌کند جدا کنیم.
  • می‌توانیم جایگزینی برای وراثت از طریق ترکیب ارائه دهیم.
  • می‌توان استراتژی‌های جدید معرفی کرد بدون این‌که مجبور باشیم Context را تغییر دهیم، بنابراین اصل Open/Closed رعایت می‌شود.

معایب

  • مشتریان باید از تفاوت بین استراتژی‌ها آگاه باشند تا بتوانند استراتژی مناسبی را انتخاب کنند.
  • اگر فقط چند الگوریتم داشته باشیم که به ندرت تغییر می‌کنند، دلیل واقعی برای پیچیده کردن برنامه با کلاس‌ها و واسط‌های جدیدی که همراه با الگو ارائه می‌شوند، وجود ندارد.
  • بسیاری از زبان‌های برنامه‌نویسی مدرن از انواع تابعی پشتیبانی می‌کنند که به شما اجازه می‌دهند نسخه‌های مختلفی از یک الگوریتم را در مجموعه‌ای از توابع ناشناس پیاده‌سازی کنید. سپس می‌توانید از این توابع دقیقاً همان‌طور که از اشیای استراتژی استفاده می‌کنید، بدون پر کردن کد خود با کلاس‌ها و واسط‌های اضافی استفاده کنید.

ارتباط با الگوهای دیگر

الگوهای Bridge، State، Strategy و Adapter ساختارهای بسیار مشابهی دارند. در واقع، این الگوها بر اساس ترکیب ساخته شده‌اند، که کار را به اشیای دیگر واگذار می‌کند. با این حال، هر کدام مشکلات مختلفی را حل می‌کنند. یک الگو فقط یک دستورالعمل برای ساختاردهی کد نیست؛ بلکه می‌تواند به توسعه‌دهندگان دیگر نشان دهد که الگو چه مشکلی را حل می‌کند.

الگوهای Command و Strategy ممکن است شبیه به نظر برسند زیرا می‌توانیم از هر دو برای پارامترگذاری یک شیء با یک عمل استفاده کنیم. اما اهداف آن‌ها بسیار متفاوت است.

ما می‌توانیم از الگوی Command برای تبدیل هر عملیاتی به یک شیء استفاده کنیم. پارامترهای عملیات به فیلدهای آن شیء تبدیل می‌شوند. این تبدیل به ما اجازه می‌دهد اجرای عملیات را به تأخیر بیندازیم، آن را در صف قرار دهیم، تاریخچه دستورات را ذخیره کنیم و دستورات را به خدمات راه دور ارسال کنیم.

از سوی دیگر، استراتژی معمولاً روش‌های مختلف انجام یک کار را توصیف می‌کند و به ما اجازه می‌دهد الگوریتم‌ها را در یک کلاس Context تعویض کنیم.

الگوی Decorator به ما اجازه می‌دهد نمای بیرونی یک شیء را تغییر دهیم، در حالی که الگوی Strategy به ما اجازه می‌دهد ساختار داخلی آن را تغییر دهیم.

الگوی State می‌تواند به‌عنوان یک توسعه از الگوی Strategy در نظر گرفته شود. هر دو الگو بر اساس ترکیب هستند: آن‌ها رفتار Context را با واگذاری برخی از کارها به اشیای کمکی تغییر می‌دهند. استراتژی این اشیاء را کاملاً مستقل و بی‌خبر از یکدیگر می‌سازد. با این حال، State وابستگی‌ها بین حالت‌های مشخص را محدود نمی‌کند و اجازه می‌دهد آن‌ها حالت Context را به دلخواه تغییر دهند.

افکار نهایی

در این مقاله، توضیح دادیم که الگوی Strategy چیست، چه زمانی باید از آن استفاده کرد و مزایا و معایب استفاده از این الگوی طراحی چیست. سپس موارد استفاده‌ای از این الگو را بررسی کردیم و نحوه ارتباط الگوی Strategy با دیگر الگوهای کلاسیک طراحی را توضیح دادیم.

لازم به ذکر است که الگوی Strategy، به همراه دیگر الگوهای طراحی که توسط Gang of Four معرفی شده‌اند، راه‌حل همه‌جانبه یا پایانی برای طراحی برنامه نیستند. تصمیم‌گیری برای استفاده از یک الگوی خاص بر عهده مهندسان است. در نهایت، این الگوها زمانی مفید هستند که به‌عنوان یک ابزار دقیق استفاده شوند، نه یک چکش بزرگ.

اگر برنامه را اجرا کنیم، ابتدا صفحه انتخاب اقلام را می‌بینیم و برنامه از ما می‌خواهد که تعدادی محصول به سبد خرید خود اضافه کنیم:

توضیح تصویر

پس از انتخاب چند مورد، برنامه از ما می‌خواهد که یک روش پرداخت انتخاب کنیم:

توضیح تصویر

اگر پرداخت Skrill را انتخاب کنیم، برنامه از ما می‌خواهد که نام کاربری و رمز عبور خود را اعتبارسنجی کنیم:

توضیح تصویر

اگر پرداخت با کارت را انتخاب کنیم، برنامه از ما می‌خواهد که اطلاعات کارت اعتباری خود را اعتبارسنجی کنیم:

توضیح تصویر

مزایا و معایب الگوی استراتژی

مزایا

  • می‌توانیم الگوریتم‌های مورد استفاده در یک شیء را در زمان اجرا تعویض کنیم.
  • می‌توانیم جزئیات پیاده‌سازی یک الگوریتم را از کدی که از آن استفاده می‌کند جدا کنیم.
  • می‌توانیم جایگزینی برای وراثت از طریق ترکیب ارائه دهیم.
  • می‌توان استراتژی‌های جدید معرفی کرد بدون این‌که مجبور باشیم Context را تغییر دهیم، بنابراین اصل Open/Closed رعایت می‌شود.

معایب

  • مشتریان باید از تفاوت بین استراتژی‌ها آگاه باشند تا بتوانند استراتژی مناسبی را انتخاب کنند.
  • بسیاری از زبان‌های برنامه‌نویسی مدرن از انواع تابعی پشتیبانی می‌کنند که به شما اجازه می‌دهند نسخه‌های مختلفی از یک الگوریتم را در مجموعه‌ای از توابع ناشناس پیاده‌سازی کنید. سپس می‌توانید از این توابع دقیقاً همان‌طور که از اشیای استراتژی استفاده می‌کنید، بدون پر کردن کد خود با کلاس‌ها و واسط‌های اضافی استفاده کنید.
  • اگر فقط چند الگوریتم داشته باشیم که به ندرت تغییر می‌کنند، دلیل واقعی برای پیچیده کردن برنامه با کلاس‌ها و واسط‌های جدیدی که همراه با الگو ارائه می‌شوند، وجود ندارد.

ارتباط با الگوهای دیگر

الگوهای Bridge، State، Strategy و Adapter ساختارهای بسیار مشابهی دارند. در واقع، این الگوها بر اساس ترکیب ساخته شده‌اند، که کار را به اشیای دیگر واگذار می‌کند. با این حال، هر کدام مشکلات مختلفی را حل می‌کنند. یک الگو فقط یک دستورالعمل برای ساختاردهی کد نیست؛ بلکه می‌تواند به توسعه‌دهندگان دیگر نشان دهد که الگو چه مشکلی را حل می‌کند.

الگوهای Command و Strategy ممکن است شبیه به نظر برسند زیرا می‌توانیم از هر دو برای پارامترگذاری یک شیء با یک عمل استفاده کنیم. اما اهداف آن‌ها بسیار متفاوت است.

ما می‌توانیم از الگوی Command برای تبدیل هر عملیاتی به یک شیء استفاده کنیم. پارامترهای عملیات به فیلدهای آن شیء تبدیل می‌شوند. این تبدیل به ما اجازه می‌دهد اجرای عملیات را به تأخیر بیندازیم، آن را در صف قرار دهیم، تاریخچه دستورات را ذخیره کنیم و دستورات را به خدمات راه دور ارسال کنیم.

از سوی دیگر، استراتژی معمولاً روش‌های مختلف انجام یک کار را توصیف می‌کند و به ما اجازه می‌دهد الگوریتم‌ها را در یک کلاس Context تعویض کنیم.

الگوی Decorator به ما اجازه می‌دهد نمای بیرونی یک شیء را تغییر دهیم، در حالی که الگوی Strategy به ما اجازه می‌دهد ساختار داخلی آن را تغییر دهیم.

الگوی State می‌تواند به‌عنوان یک توسعه از الگوی Strategy در نظر گرفته شود. هر دو الگو بر اساس ترکیب هستند: آن‌ها رفتار Context را با واگذاری برخی از کارها به اشیای کمکی تغییر می‌دهند. استراتژی این اشیاء را کاملاً مستقل و بی‌خبر از یکدیگر می‌سازد. با این حال، State وابستگی‌ها بین حالت‌های مشخص را محدود نمی‌کند و اجازه می‌دهد آن‌ها حالت Context را به دلخواه تغییر دهند.

نتیجه‌گیری

در این مقاله، توضیح دادیم که الگوی Strategy چیست، چه زمانی باید از آن استفاده کرد و مزایا و معایب استفاده از این الگوی طراحی چیست. سپس موارد استفاده‌ای از این الگو را بررسی کردیم و نحوه ارتباط الگوی Strategy با دیگر الگوهای کلاسیک طراحی را توضیح دادیم.

لازم به ذکر است که الگوی Strategy، به همراه دیگر الگوهای طراحی که توسط Gang of Four معرفی شده‌اند، راه‌حل همه‌جانبه یا پایانی برای طراحی برنامه نیستند. تصمیم‌گیری برای استفاده از یک الگوی خاص بر عهده مهندسان است. در نهایت، این الگوها زمانی مفید هستند که به‌عنوان یک ابزار دقیق استفاده شوند، نه یک چکش بزرگ.

©دوات با هدف دسترس‌پذیر کردن دانش انگلیسی در حوزه صنعت نرم‌افزار وجود آمده است. در این راستا از هوش مصنوعی برای ترجمه گلچینی از مقالات مطرح و معتبر استفاده می‌شود. با ما در تماس باشید و انتقادات و پیشنهادات خود را از طریق صفحه «تماس با ما» در میان بگذارید.