Решение на языке C#
Теперь приведем решение задачи OOP1Creat1 на языке C#.
Несмотря на то, что в нем, как и в решении для языка C++,
потребуется реализовать систему классов в полном объеме, включая все
абстрактные классы и методы, решение получится достаточно коротким,
благодаря компактности конструкций самого языка и обширному набору
функций из стандартной библиотеки платформы .NET.
Будем по-прежнему использовать среду Visual Studio Code
(задания на языке C# можно также выполнять в средах Visual Studio и SharpDevelop).
Начнем с обсуждения заготовки, созданной для этого задания.
Ниже приводится текст этой заготовки, за исключением начальных директив
using для подключения пространств имен, а также директивы namespace определения
пространства имен для класса MyTask:
public class MyTask : PT
{
public abstract class Product
{
public abstract string GetInfo();
public abstract void Transform();
}
// Implement the ConcreteProduct1
// and ConcreteProduct2 descendant classes
public abstract class Creator
{
protected abstract Product FactoryMethod(string info);
public string AnOperation(string info)
{
Product p = FactoryMethod(info);
p.Transform();
p.Transform();
return p.GetInfo();
}
}
// Implement the ConcreteCreator1
// and ConcreteCreator2 descendant classes;
// the AnOperation method should not be
// overridden in these classes
public static void Solve()
{
Task("OOP1Creat1");
}
}
В заготовках для языка C# все требуемые классы надо описывать в
виде внутренних классов класса MyTask, который является потомком
класса PT и содержит все элементы решения задачи. В частности, функция
Solve тоже является методом класса MyTask. В остальном созданная
заготовка очень похожа на заготовку для языка C++. Отметим лишь, что в
языке C# все методы класса описываются непосредственно внутри
описания класса и что после описаний классов не надо указывать точку с
запятой. Некоторые отличия имеются также в правилах указания
модификаторов доступа. Для языка C# модификатор override при
переопределении виртуальных методов является обязательным, а в
абстрактных методах вместо модификатора virtual используется
модификатор abstract (а конструкция «= 0» не указывается).
Приведем вариант реализации конкретных классов-продуктов
ConcreteProduct1 и ConcreteProduct2:
public class ConcreteProduct1 : Product
{
string info;
public override string GetInfo()
{
return info;
}
public ConcreteProduct1(string info)
{
this.info = info.ToLower();
}
public override void Transform()
{
StringBuilder s = new StringBuilder(info);
for (int i = s.Length - 2; i >= 0; i--)
if (s[i] != ' ')
s.Insert(i + 1, " ");
info = s.ToString();
}
}
public class ConcreteProduct2 : Product
{
string info;
public override string GetInfo()
{
return info;
}
public ConcreteProduct2(string info)
{
this.info = info.ToUpper();
}
public override void Transform()
{
StringBuilder s = new StringBuilder(info);
for (int i = s.Length - 2; i >= 0; i--)
if (s[i] != '*')
s.Insert(i + 1, "**");
info = s.ToString();
}
}
Для того чтобы обеспечить более эффективное преобразование поля info
в методе Transform, мы используем класс StringBuilder, методы которого
(в отличие от аналогичных методов класса string) преобразуют исходный объект
StringBuilder, а не создают новые строковые объекты.
Реализация конкретных классов-создателей ConcreteCreator1 и
ConcreteCreator2, как и для предыдущих языков, сложностей не
представляет:
public class ConcreteCreator1 : Creator
{
protected override Product FactoryMethod(string info)
{
return new ConcreteProduct1(info);
}
}
public class ConcreteCreator2 : Creator
{
protected override Product FactoryMethod(string info)
{
return new ConcreteProduct2(info);
}
}
Организация ввода и вывода данных для языка C# похожа на вариант
для языка Python, однако, чтобы обеспечить строгую типизацию, для ввода
данных каждого типа необходимо использовать соответствующую
функцию: GetInt(), GetDouble, GetString() и т. д. Вывод выполняется
функцией Put с произвольным числом параметров. Используя эти средства
ввода-вывода, получаем следующий вариант функции Solve:
public static void Solve()
{
Task("OOP1Creat1");
var c1 = new ConcreteCreator1();
var c2 = new ConcreteCreator2();
for (int i = 0; i < 5; i++)
{
string s = GetString();
Put(c1.AnOperation(s), c2.AnOperation(s));
}
}
От варианта для языка C++ данный фрагмент отличается также другим
синтаксисом вызова конструктора, в котором используется ключевое слово
new (как при создании в C++ объекта в динамической памяти). Такой
синтаксис подчеркивает важную особенность объектной модели языка C#,
в которой все классы являются ссылочными типами и их объекты всегда
размещаются в динамической памяти.
Очень важным является то обстоятельство, что программист не должен
заботиться об освобождении памяти, выделенной для хранения объектов,
поскольку за это действие отвечает специальная подсистема платформы .NET сборщик мусора
(garbage collector, GC). Наличие сборщика мусора значительно упрощает разработку программ.
Напомним, что в языке C++ тоже имеются средства для автоматического освобождения памяти
умные указатели, однако при их неправильном использовании в программе
всё равно могут происходить утечки памяти. В языке C# и многих других современных языках
(в том числе Python, Ruby и Java, которые рассматриваются в данном разделе),
освобождение памяти выполняется полностью автоматически. Платой за подобную автоматизацию
является некоторое (хотя и очень незначительное) замедление работы программы
во время активизации сборщика мусора и выполнения им необходимых действий по освобождению памяти.
При описании объектов-создателей c1 и c2 мы использовали ключевое
слово var, позволяющее не указывать тип описываемой переменной, если
его можно вывести из инициализирующего выражения. Заметим, что это
же слово var можно было использовать и при описании переменных i и s.
Запустив полученный вариант решения, мы увидим окно задачника с
сообщением о том, что задание выполнено.
|