OOP

Всё больше наших коллег на практике предпочитают избегать применения операторов if. Эти условия, по их мнению, усложняют и прибавляют багов в наши приложения.

Но условия лежат в основе программирования, и мы не можем полностью избавиться от операторов if — мы можем лишь сократить их использование.

Что такое словарь?

Словарь — это структура данных, предназначенная для хранения группы объектов. Он может использоваться для маппинга, кэша в оперативной памяти, таблиц и т.д. Объекты хранятся в нём как коллекция пар ключ/значение, что очень удобно и характерно для разных объектно-ориентированных языков программирования. Таким же образом — в виде пар ключ/значение — могут быть заданы делегаты.

Что такое делегат?

«Делегат — это объект, который ссылается на метод. Или даже можно сказать, что это переменная ссылочного типа, которая содержит ссылку на методы. Делегаты в C# схожи с указателем на функцию в C/C++. Он помогает определить, какой метод должен вызываться при срабатывании события».

Есть два типа делегатов, которые нужны для наших примеров: Actionи Func. Action используется для методов void, а Func — для методов возвращаемого типа return.

Например:

static void Main(string[] args)
{
    Dictionary<string, Action> dict = new Dictionary<string, Action>();
    dict.Add("foo", bar);
    dict["foo"].Invoke();
    
    Console.WriteLine("World");
    
    Console.ReadLine();
}   

static void bar()
{
    Console.WriteLine("Hello");
}

Здесь определяется словарь с типом <string, Action> и к нему добавляется элемент, а затем выполняется метод для вызова dict[“foo”]. Можем использовать dict[“foo”]() как альтернативу dict[“foo”].Invoke().

Результат:

Какой полезный метод, правда?:)

Пример

Предположим, у нас есть модуль генерации отчётов, который создаёт периодические отчёты: ежедневные, еженедельные, ежемесячные, ежегодные и т.д. Причём они не содержат параметрического или возвращаемого типа.

Сначала он выглядит вот так:

using System;
using System.Collections.Generic;

namespace DictionaryTraining
{
    public class Program
    {
        static void Main(string[] args)
        {
            Reporter reporter = new Reporter();
            ReportType reportType = ReportType.Monthly;

            PrepareReport(reportType);

            Console.ReadLine();
        }

        private static void PrepareReport(ReportType reportType)
        {
            Reporter reporter = new Reporter();

            if (reportType == ReportType.Daily)
            {
                reporter.GetDailyReport();
            }
            else if (reportType == ReportType.Weekly)
            {
                reporter.GetWeeklyReport();
            }
            else if (reportType == ReportType.Monthly)
            {
                reporter.GetMonthlyReport();
            }
            else if (reportType == ReportType.Annual)
            {
                reporter.GetAnnualReport();
            }
           
        }
    }

    public class Reporter
    {
        public void GetDailyReport()
        {
            Console.WriteLine("Daily report is preparing...");
        }

        public void GetWeeklyReport()
        {
            Console.WriteLine("Weekly report is preparing...");
        }

        public void GetMonthlyReport()
        {
            Console.WriteLine("Monthly report is preparing...");
        }

        public void GetAnnualReport()
        {
            Console.WriteLine("Annual report is preparing...");
        }
    }

    public enum ReportType
    {
        Daily,
        Weekly, 
        Monthly,
        Annual
    }
}

Теперь у нас есть класс Reporter с разными методами для подготовки отчётов.

Для вызова определённого метода в соответствии с типом каждого отчёта используется метод PrepareReport. Для этого будет задействовано множество операторов if-else.

Ну а мы попробуем вместо них использовать словарь и делегаты:

using System;
using System.Collections.Generic;

namespace DictionaryTraining
{
    public class Program
    {
        static Dictionary<ReportType, Action> dictReports = new Dictionary<ReportType, Action>();
        static void Main(string[] args)
        {
            Reporter reporter = new Reporter();
            dictReports.Add(ReportType.Daily, new Action(reporter.GetDailyReport));
            dictReports.Add(ReportType.Weekly, new Action(reporter.GetWeeklyReport));
            dictReports.Add(ReportType.Monthly, new Action(reporter.GetMonthlyReport));
            dictReports.Add(ReportType.Annual, new Action(reporter.GetAnnualReport));

            dictReports[ReportType.Weekly]();

            Console.ReadLine();
        }
    }

    public class Reporter
    {
        public void GetDailyReport()
        {
            Console.WriteLine("Daily report is preparing...");
        }

        public void GetWeeklyReport()
        {
            Console.WriteLine("Weekly report is preparing...");
        }

        public void GetMonthlyReport()
        {
            Console.WriteLine("Monthly report is preparing...");
        }

        public void GetAnnualReport()
        {
            Console.WriteLine("Annual report is preparing...");
        }
    }

    enum ReportType
    {
        Daily,
        Weekly, 
        Monthly,
        Annual
    }
}

Словарь с типом <ReportType, Action> будет использоваться в качестве делегата, а методы создания отчёта будут вызываться без необходимости проверять тип каждого отчёта.

На мой взгляд, это более читаемый и лёгкий в сопровождении код. Меньше строк — больше ясности.

Делегаты с возвращаемым типом

Если методы для подготовки отчётов имеют возвращаемый тип, должен использоваться делегат Func (но все методы должны иметь те же параметрические и возвращаемый типы).

Вот так:

using System;
using System.Collections.Generic;

namespace DictionaryTraining
{
    public class Program
    {
        static Dictionary<ReportType, Func<int, string>> dictReports = new Dictionary<ReportType, Func<int, string>>();
        static void Main(string[] args)
        {
            Reporter reporter = new Reporter();
            dictReports.Add(ReportType.Daily, new Func<int, string>(reporter.GetDailyReport));
            dictReports.Add(ReportType.Weekly, new Func<int, string>(reporter.GetWeeklyReport));
            dictReports.Add(ReportType.Monthly, new Func<int, string>(reporter.GetMonthlyReport));
            dictReports.Add(ReportType.Annual, new Func<int, string>(reporter.GetAnnualReport));

            dictReports[ReportType.Daily](60);

            Console.ReadLine();
        }
    }

    public class Reporter
    {
        public string GetDailyReport(int dayOfYear)
        {
            Console.WriteLine("Daily report is preparing...");
            return "report of day: " + dayOfYear;
        }

        public string GetWeeklyReport(int weekOfYear)
        {
            Console.WriteLine("Weekly report is preparing...");
            return "report of week: " + weekOfYear;
        }

        public string GetMonthlyReport(int monthOfYear)
        {
            Console.WriteLine("Monthly report is preparing...");
            return "report of month: " + monthOfYear;
        }

        public string GetAnnualReport(int year)
        {
            Console.WriteLine("Annual report is preparing...");
            return "report of year: " + year;
        }
    }

    enum ReportType
    {
        Daily,
        Weekly, 
        Monthly,
        Annual
    }
}

И вот результат:

А сколько ещё есть вариантов применения словаря с точки зрения роста показателей производительности!

Читайте также:


Перевод статьи Muhammed Hilmi Koca: Effective Dictionary Usage(C#): Avoid If Statements

Предыдущая статьяКак реализовать древовидную сортировку в стандартной библиотеке Go
Следующая статьяЗнакомство с классами в Python