开闭原则c#
open closed principle c#
我有 3 个 classes(1 个抽象(服务)和 2 个衍生物)。另外,我有 2 种不同的输出格式 (UA/EN)。而且我不知道如何重写我的代码以使其遵循开放封闭原则。例如,如果我想添加德语输出格式。我需要编辑每个 class.
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!");
}
}
}
}
首先,我想鼓励不要在没有需求或目标的情况下进行重构。如果您试图重构此代码以使其可扩展以用于您不知道需要扩展的内容,那么您不仅可能会浪费精力 (YAGNI),而且最终可能会得到更难更改的代码以后您可能需要的其他方式。
因此,为了这个答案的目的,我假设要求是使此代码可扩展(开放扩展)。而且您需要扩展的是支持的格式。
我们将首先定义一个新的抽象classFormatter
接口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
这样的解决方案会使这更容易。无论如何,我相信这个答案可以解决问题。
我有 3 个 classes(1 个抽象(服务)和 2 个衍生物)。另外,我有 2 种不同的输出格式 (UA/EN)。而且我不知道如何重写我的代码以使其遵循开放封闭原则。例如,如果我想添加德语输出格式。我需要编辑每个 class.
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!");
}
}
}
}
首先,我想鼓励不要在没有需求或目标的情况下进行重构。如果您试图重构此代码以使其可扩展以用于您不知道需要扩展的内容,那么您不仅可能会浪费精力 (YAGNI),而且最终可能会得到更难更改的代码以后您可能需要的其他方式。
因此,为了这个答案的目的,我假设要求是使此代码可扩展(开放扩展)。而且您需要扩展的是支持的格式。
我们将首先定义一个新的抽象class接口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
这样的解决方案会使这更容易。无论如何,我相信这个答案可以解决问题。