首页 > 解决方案 > 开闭原则 c#

问题描述

我有 3 个类(1 个 absrtact(服务)和 2 个派生类)。另外,我有 2 种不同的输出格式(UA/EN)。而且我不知道如何重新编写我的代码以使其遵循开放封闭原则。例如,如果我想添加德语输出格式。我将需要编辑每个班级。

using System;
using System.Globalization;
namespace naslidov
{
    public abstract class Services
    {
        public string title;
        public decimal price;

        public Services(string title, decimal price)
        {
            this.title = title;
            this.price = price;
        }

        public virtual string ToEnglish()
        {        
            return $" ";
        }

        public virtual string ToUkraine()
        {
            return $"";
        }
    }

    public class Food : Services
    {
        public DateTime expirationDate;
        public Food(string title, decimal price, DateTime expirationDate)
            : base(title, price)
        {
            this.title = title;
            this.price = price;
            this.expirationDate = expirationDate;
        }

        public override string ToEnglish()
        {
            return $"{base.title}      |{price.ToString("N", CultureInfo.InvariantCulture)} uan | {expirationDate.ToString("d", CultureInfo.CreateSpecificCulture("ja-JP"))} |------ ";
        }

        public override string ToUkraine()
        {
            return $"{base.title}      |{price.ToString("F", CultureInfo.InvariantCulture).Replace(".", ",")} uan | {expirationDate.ToString("dd.MM.yyyy")}| ------ ";
        }
    }


    public class HouseholdAppliance : Services
    {
        public int warrantyPeriodInMonths;

        public HouseholdAppliance(string title, decimal price, int warrantyPeriodInMonths)
            : base(title, price)
        {
            this.title = title;
            this.price = price;
            this.warrantyPeriodInMonths = warrantyPeriodInMonths;
        }

        public override string ToEnglish()
        {
            return $"{base.title}      |{price.ToString("N", CultureInfo.InvariantCulture)} uan | ------ |{warrantyPeriodInMonths} ";
        }

        public override string ToUkraine()
        {
            return $"{base.title}      |{price.ToString("F", CultureInfo.InvariantCulture).Replace(".", ",")} uan | ------ |{warrantyPeriodInMonths} ";
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter region(UA/EN):");
            string user_input = Console.ReadLine();


            DateTime date1 = new DateTime(2002, 3, 25);
            DateTime date2 = new DateTime(2022, 8, 17);
            DateTime date3 = new DateTime(2005, 1, 10);

            Services first = new Food("apple", 105324660120.58m, date1);
            Services second = new Food("bananas", 3045.21m, date2);
            Services third = new Food("nuts", 308540m, date3);

            Services nofrst = new HouseholdAppliance("television", 25547.54m, 12);
            Services noscd = new HouseholdAppliance("pilosos", 2756854m, 24);
            Services nothir = new HouseholdAppliance("notebook", 32248, 36);


            Services[] fullservices = new Services[] { first, second, third, nofrst, noscd, nothir };
            Console.WriteLine("title    |   price     |   expirationDate   |   warrantyPeriodInMonths");
            Console.WriteLine("-----------------------------------------------------------------------");

            if (user_input == "EN")
            {
                foreach (Services fullservice in fullservices)
                {
                    Console.WriteLine(fullservice.ToEnglish());
                }
            }
            if (user_input == "UA")
            {
                foreach (Services fullservice in fullservices)
                {
                    Console.WriteLine(fullservice.ToUkraine());
                }
            }
            else if (user_input != "UA" && user_input != "EN")
            {
                Console.WriteLine(" Sorry, wrong input!");
            }
        }
    }
}

标签: c#classabstract-class

解决方案


首先,我想鼓励永远不要在没有要求或目标的情况下进行重构。如果您尝试重构此代码以使其可扩展以用于您不知道需要扩展的内容,那么您不仅可能会浪费精力(YAGNI),而且您最终可能会得到更难更改的代码以您以后可能需要的其他方式。

因此,出于此答案的目的,我将假设要求是使此代码可扩展(对扩展开放)。而您需要扩展的是支持的格式。

我们将从定义一个新的抽象类Formatter接口开始,该接口IFormat将用作添加新格式的扩展点。理想情况下,这IFormat不应该依赖于任何具体的(具体的,而不是抽象的)Services,也不应该Services知道任何具体IFormat的。也就是说,我们希望将这些扩展为尽可能独立。

现在,具体Services需要格式化什么?我可以在代码中看到您需要知道日期和价格的格式。因此,让我们给出将它们格式化为的方法IFormat

    public interface IFormat
    {
        string FormatDate(DateTime date);

        string FormatPrice(decimal price);
    }

添加任何其他有意义的方法。我为这种情况添加了最小值。

我们可以继续为英语和乌克兰语实现一个格式化程序。请原谅我的命名约定。

    public class FormatterEnglish : IFormat
    {
        public string FormatDate(DateTime date)
        {
            return date.ToString("d", CultureInfo.CreateSpecificCulture("ja-JP"));
        }

        public string FormatPrice(decimal price)
        {
            return price.ToString("N", CultureInfo.InvariantCulture);
        }
    }

    public class FormatterUkrane : IFormat
    {
        public string FormatDate(DateTime date)
        {
            return date.ToString("dd.MM.yyyy");
        }

        public string FormatPrice(decimal price)
        {
            return price.ToString("F", CultureInfo.InvariantCulture).Replace(".", ",");
        }
    }

现在,让我们重新工作Services以使用它。每种格式将不再有一个方法,而是一个带有IFormat参数的方法:

    public abstract class Services
    {
        public decimal price;
        public string title;

        public Services(string title, decimal price)
        {
            this.title = title;
            this.price = price;
        }

        public abstract string ToString(IFormat formatter);
    }

而且,当然,我们需要在以下位置实现它HouseholdAppliance

        public override string ToString(IFormat formatter)
        {
            return $"{base.title}      |{formatter.FormatPrice(price)} uan | ------ |{warrantyPeriodInMonths} ";
        }

并且Food

        public override string ToString(IFormat formatter)
        {
            return $"{base.title}      |{formatter.FormatPrice(price)} uan | {formatter.FormatDate(expirationDate)} |------ ";
        }

要选择我们的IFormat,我建议使用工厂方法。例如:

        private static IFormat? CreateFormatter(string formatName)
        {
            if (formatName == "EN")
            {
                return new FormatterEnglish();
            }

            if (formatName == "UA")
            {
                return new FormatterUkrane();
            }

            return null;
        }

您可能还对使用类型发现以及在自定义属性中指定格式名称感兴趣。这超出了这个答案的范围。

最后,您可以像这样使用它:

            var formatter = CreateFormatter(user_input);
            if (formatter == null)
            {
                Console.WriteLine(" Sorry, wrong input!");
                return;
            }

            foreach (Services fullservice in fullservices)
            {
                Console.WriteLine(fullservice.ToString(formatter));
            }

再次查看代码,可以Services. IFormat需要模板和数据。像这样的解决方案FormatWith会让这更容易。无论如何,我相信这个答案可以解决问题。


推荐阅读