Решения на языках C# и PascalABC.NET
Теперь приведем решение задачи OOP1Creat1 на языке C#.
Несмотря на то, что в нем, как и в решении для языка C++,
потребуется реализовать систему классов в полном объеме, включая все
абстрактные классы и методы, решение получится достаточно коротким,
благодаря компактности конструкций самого языка и обширному набору
функций из стандартной библиотеки платформы .NET.
В завершающей части данного пункта мы приведем решение на языке PascalABC.NET,
который имеет много общего с языком C# и также позволяет
использовать библиотеку платформы .NET.
При решении задачи на языке C# будем по-прежнему использовать среду Visual Studio Code
(задания на этом языке можно также выполнять в средах Visual Studio и SharpDevelop).
Начнем с обсуждения заготовки, созданной для этого задания.
Ниже приводится текст этой заготовки, за исключением начальных директив
using для подключения пространств имен, а также директивы namespace определения
пространства имен для класса MyTask:
[C#] 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:
[C#] 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, как и для предыдущих языков, сложностей не
представляет:
[C#] 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:
[C#] 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.
Запустив полученный вариант решения, мы увидим окно задачника с
сообщением о том, что задание выполнено.
Теперь обратимся к языку PascalABC.NET. Версия 1.1 задачника PT for OOP интегрирована в среду
PascalABC.NET и позволяет создавать развернутые заготовки для всех заданий.
Поскольку система PascalABC.NET активно используется в России при обучении школьников,
реализованы два варианта заготовок, в которых комментарии выводятся как на русском, так и на английском языке
(выбор варианта заготовки зависит от текущих языковых настроек среды PascalABC.NET).
Приведем заготовку, в которой используется русские комментарии.
[PascalABC.NET] uses PT4;
type
Product = abstract class
public
function GetInfo: string; abstract;
procedure Transform; abstract;
end;
// Реализуйте классы-потомки ConcreteProduct1 и ConcreteProduct2
Creator = abstract class
protected
function FactoryMethod(info: string): Product; abstract;
public
function AnOperation(info: string): string;
begin
var p := FactoryMethod(info);
p.Transform;
p.Transform;
Result := p.GetInfo;
end;
end;
// Реализуйте классы-потомки ConcreteCreator1 и ConcreteCreator2.
// В этих классах не следует переопределять метод AnOperation
begin
Task('OOP1Creat1');
end.
Созданную заготовку можно запустить на выполнение, чтобы ознакомиться с формулировкой задачи
и примером исходных данных и результатов. Напомним, что для запуска программы в среде PascalABC.NET
достаточно нажать клавишу F9.
Как и для языка C#, заготовка для языка PascalABC.NET содержит
полные описания абстрактных классов Product и Creator, причем в классе Creator
уже реализован метод AnOperation, который не требуется изменять.
Отличия от языка C# проявляются лишь в деталях синтаксиса; в частности, по-другому
члены класса с одной и той же областью видимости оформляются в виде группы,
перед которой указывается соответствующий модификатор (private, protected, internal, public).
Модификатор abstract указывается после заголовка абстрактного метода;
как и для языка C#, все абстрактные методы считаются виртуальными, причем
модификатор virtual указывать необязательно (но при переопределении абстрактных методов
необходимо указывать модификатор override).
При описании класса указываются его поля и заголовки методов, причем можно либо
сразу приводить реализацию методов (как в языке C#), либо описывать методы
после описании класса, снабжая при этом имена методов именем класса, например, Creator.AnOperation.
В заготовках реализация методов всегда приводится непосредственно при описании класса.
Следуя этому варианту реализации методов, приведем описания конкретных классов-продуктов
(эти описания должны располагаться в том же разделе type, что и описание класса Product):
[PascalABC.NET] ConcreteProduct1 = class(Product)
private
info: string;
public
constructor Create(info: string);
begin
self.info := info.ToLower();
end;
function GetInfo: string; override;
begin
Result := info;
end;
procedure Transform; override;
begin
for var i := info.Length - 1 downto 1 do
if info[i] <> ' ' then
Insert(' ', info, i + 1);
end;
end;
ConcreteProduct2 = class(Product)
private
info: string;
public
constructor Create(info: string);
begin
self.info := info.ToUpper();
end;
function GetInfo: string; override;
begin
Result := info;
end;
procedure Transform; override;
begin
for var i := info.Length - 1 downto 1 do
if info[i] <> '*' then
Insert('**', info, i + 1);
end;
end;
Следует обратить внимание на то, что при описании класса-потомка имя класса-предок
указывается в круглых скобках, а описания конструкторов начинаются
со специального слова constructor, причем в качестве имени используется имя Create.
Возвращаемое значение функции надо присваивать особой переменной Result,
которую не нужно описывать.
В данной реализации метода Transtorm мы не использовали класс StringBuilder,
поскольку строки в PascalABC.NET являются изменяемыми, а одной из функций,
позволяющей изменить строку, является функция Insert. При работе со строками
в PascalABC.NET необходимо учитывать, что они индексируются от 1, и этот же
способ индексирования используется в ряде стандартных функций, в частности, в
использованной нами функции Insert.
Теперь приведем реализации конкретных классов-создателей
(их следует указать после реализации абстрактного класса Creator):
[PascalABC.NET] ConcreteCreator1 = class(Creator)
protected
function FactoryMethod(info: string): Product; override;
begin
Result := new ConcreteProduct1(info);
end;
end;
ConcreteCreator2 = class(Creator)
protected
function FactoryMethod(info: string): Product; override;
begin
Result := new ConcreteProduct2(info);
end;
end;
Здесь следует обратить внимание на то, что вызов конструкторов в языке PascalABC.NET
может выполняться по тем же правилам, что и в языке C# (с использованием слова new), несмотря
на то что при описании конструкторов используется слово Create.
Запустив полученный вариант программы, мы можем убедиться, что описания новых классов
не содержат синтаксических ошибок.
Осталось проверить правильность их реализации, обработав требуемым образом исходные данные.
Эти действия надо выполнить в основном блоке программы после вызова функции Task
(который уже содержится в заготовке):
[PascalABC.NET] begin
Task('OOP1Creat1');
var c1 := new ConcreteCreator1;
var c2 := new ConcreteCreator2;
loop 5 do
begin
var s := ReadString;
Print(c1.AnOperation(s), c2.AnOperation(s));
end;
end.
Для ввода исходных данных с применением задачника предусмотрен набор функций
(ReadInteger, ReadReal, ReadChar, ReadString и др.), а для вывода результатов
функция Print, в которой можно указывать произвольное число параметров.
Обратите внимание на использование цикла loop, который является упрощенным вариантом
цикла for и используется в случае, когда требуется выполнить требуемое число одинаковых действий.
Запустив данный вариант программы, мы получим сообщение о том, что задание выполнено.
|