#c# #class #abstract-class
Вопрос:
У меня есть 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" amp;amp; user_input != "EN")
{
Console.WriteLine(" Sorry, wrong input!");
}
}
}
}
Комментарии:
1. Есть ли у вас какие-либо конкретные проблемы, которые заставляют вас думать, что ваш код не соответствует открытому/закрытому?
2. Я так думаю, например, если я хочу добавить формат вывода на немецком языке. Мне нужно будет отредактировать каждый класс
Ответ №1:
Прежде всего, я хочу призвать никогда не проводить рефакторинг без каких-либо требований или целей. Если вы пытаетесь реорганизовать этот код, чтобы сделать его расширяемым для чего-то, о чем вы не знаете, что его нужно расширять, вы, скорее всего, не только потратите впустую усилия (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
сделало бы это проще. В любом случае, я считаю, что этот ответ отвечает на вопрос так, как он есть.
Комментарии:
1. @Святослав, ты спрашиваешь, почему это личное? Я заявил об этом внутри
Program
, вот и все. Вы спрашиваете о кавычках? Это потому, что оно может быть нулевым. Я написал это в контексте, который можно аннулировать. Кроме того, я не знаю, на что вы, возможно, указываете. На всякий случай я упомяну, что вы можете продвинуть его в заводской класс, либо инициализированный списком типов форматирования, либо с помощью обнаружения типов.2. 1 ОТВЕТ — в каких классах я должен использовать (частный статический форматер? CreateFormatter(имя формата строки)…..) потому что у меня есть ошибка im главная : Ошибка CS0103 Имя «CreateFormatter» не существует в текущем контексте, и 1 Состояние предупреждения Предупреждение CS8632 Аннотация для типов ссылок, допускающих обнуление, должна использоваться только в коде в контексте аннотаций «#обнуление». Пожалуйста, помогите.
3. @Святослав, как я уже сказал выше, я объявил об этом (
CreateFormatter
) внутриProgram
. Вот где он используется. Его можно перенести в другой класс и опубликовать (я предлагаю выделенный классFormatterFactory
), а затем вызвать его оттуда. И, что касается контекста, допускающего обнуление, вы можете либо добавить аннотацию#nullable enable
в начале файла, которая будет работать, если вы используете C# 8.0 или новее. Или просто удалите кавычки»?», если вы используете более старый C#.4. БОЛЬШОЕ ВАМ СПАСИБО, все работает, я просто случайно поставил этот метод в основное
5. @Святослав, знаешь что? Я только что понял
Formatter
, что это может быть интерфейс. Я думаю, что внесу эту правку. Выполнено.