Перевод статьи From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks
В прошлый раз мы с вами рассмотрели абстрактные классы, но уже на этой неделе мы обсудим даже более абстрактный тип классов (чем абстрактные классы): статические классы. Так же, мы рассмотрим анти-конструкторы C#, которые более известны, как «деструкторы», и, в дополнение ко всему, мы рассмотрим некоторые забавные трюки при работе с конструкторами классов.
Статические классы
Давайте начнём сегодняшнюю статью с «даже более абстрактных» классов: статических классов. Работая с абстрактными классами, вы всё ещё можете расширять их и создавать экземпляры дочерних классов:
abstract class Shape
{
}
class Square : Shape // legal
{
}
new Shape(); // illegal
new Square(); // legal
Работая со статическими классами, вы не можете ни инстанциировать, ни наследовать их. Вы никогда не сможете создать экземпляр подобного класса:
static class Shape
{
}
class Square : Shape // illegal
{
}
new Shape(); // illegal
new Square(); // illegal
Но для чего вообще могут понадобиться подобные классы? Подобные классы могут быть хорошим местом для хранения статических функций, полей и свойств. И, так как вы не можете создавать экземпляры подобных классов, в них запрещено использование не статических полей любых типов данных. Конструкторы экземпляров класса так же запрещены, т.к. класс автоматически приравнивается к sealed классам. Довольно популярный пример использования подобных классов — класс Math. Вам вряд ли когда-либо нужно будет создать экземпляр этого класса, но внутри него содержится большое количество полезных статических функций (например Abs) и полей (например PI). Вот, как может выглядеть реализация подобного класса:
public static class Math
{
// remember that 'const' is automatically static
// also, this would surely have more precision
public const double PI = 3.1415926;
public static double Abs(double value)
{
return value >= 0 ? value : -value;
}
}
new Math(); // illegal
В AS3 по-умолчанию нет поддержки статических классов на этапе компиляции, но вы можете обойти это ограничение, используя проверки на этапе проигрывания (run-time). Всё, что вам нужно будет сделать — это объявить класс, как final, и всегда бросать ошибку в конструкторе этого класса:
public final class Math
{
public static const PI:Number = 3.1415926;
public function Math()
{
throw new Error("Math is static");
}
public static function abs(value:Number): Number
{
return value >= 0 ? value : -value;
}
}
new Math(); // legal, but throws an exception
Деструкторы
Следующим пунктом в сегодняшней программе идут деструкторы, которые являются «анти-конструкторами», потому что они отвечают за уничтожение класса, а не за его создание, как в случае с обычными конструкторами. Деструкторы вызываются сборщиками мусора (Garbage Collector) непосредственно перед тем, как объект освобождает занимаемую им память. Вот, как они выглядят:
class TemporaryFile
{
~TemporaryFile()
{
// cleanup code goes here
}
}
Для создания деструктора, достаточно добавить ~ к имени класса. Деструктор может быть только один, и с ним нельзя использовать модификаторы доступа. Обычно, необходимости в создании деструкторов нет, но в некоторых случаях они могут быть полезными, в качестве способа очистки ресурсов после использования класса. В примере ниже деструктор используется для удаления из операционной системы временного файла, который в другом случае не будет удалён, т.к. GC никогда не сделает этого:
using System.IO;
class TemporaryFile
{
public String Path { get; private set; }
TemporaryFile(String path)
{
Path = path;
File.Create(path);
}
~TemporaryFile()
{
File.Delete(Path);
}
}
// Create the temporary file
TemporaryFile temp = new TemporaryFile("/path/to/temp/file");
// ... use the temporary file
// Remove the last reference to the TemporaryFile instance
// GC will now collect temp, call the destructor, and delete the file
temp = null;
В данном примере класс TemporaryFile создаёт файл в конструкторе экземпляра класса, и удаляет файл, когда на экземпляр класса нет ссылок и класс готов быть собранным GC, чтобы освободить память. В AS3 нет функций, которые бы вызывались, когда экземпляр класса готов быть собранным GC. Обычно, чтобы реализовать подобное поведение, необходимо вручную создавать и вызывать «псевдо-деструкторы» (обычно их называют dispose или destroy):
import flash.filesystem;
class TemporaryFile
{
private var _path:String;
public function get path(): String { return _path; }
public function set path(p:String): void { _path = p; }
private var _file:File;
function TemporaryFile(path:String)
{
_path = path;
_file = new File(path);
var stream:FileStream = new FileStream();
stream.open(_file, FileMode.WRITE);
}
function dispose(): void
{
_file.deleteFile();
}
}
// Create the temporary file
var temp:TemporaryFile = new TemporaryFile("/path/to/temp/file");
// ... use the temporary file
// Manually call dispose() to delete the temporary file
temp.dispose();
// Remove the last reference to the TemporaryFile instance
// GC will now collect temp
temp = null;
Трюки при работе с конструкторами
Последней темой на сегодня будут трюки при работе с конструкторами. Мы уже разбирали способ вызова конструктора базового класса, используя ключевое слово base (аналогично использованию ключевого слова super в AS3):
class Polygon
{
Polygon(int numSides)
{
}
}
class Triangle : Polygon
{
Triangle()
: base(3) // call the Polygon constructor
{
}
}
Так же, мы рассматривали возможность создания более чем одного конструктора, используя «перегрузку»:
class Vector3
{
double X;
double Y;
double Z;
Vector3()
{
X = 0;
Y = 0;
Z = 0;
}
Vector3(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
Vector3(Vector3 vec)
{
X = vec.X;
Y = vec.Y;
Z = vec.Z;
}
}
Vector3 v1 = new Vector3(); // (0, 0, 0)
Vector3 v2 = new Vector3(1, 2, 3); // (1, 2, 3)
Vector3 v3 = new Vector3(v2); // (1, 2, 3)
Обычно этот способ приводит к дублированию кода внутри конструкторов. Но, т.к. версия конструктора, которая принимает 3 параметра наиболее общая из всех, то можно просто вызывать её из 2 других конструкторов:
class Vector3
{
double X;
double Y;
double Z;
Vector3()
: this(0, 0, 0)
{
}
Vector3(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
Vector3(Vector3 vec)
: this(vec.X, vec.Y, vec.Z)
{
}
}
Vector3 v1 = new Vector3(); // (0, 0, 0)
Vector3 v2 = new Vector3(1, 2, 3); // (1, 2, 3)
Vector3 v3 = new Vector3(v2); // (1, 2, 3)
Мы можем использовать this() для вызова других конструкторов в рамках нашего класса (по аналогии с base(), что позволяло вызывать конструктор родительского класса). И снова, в AS3 не было подобного функционала по-умолчанию, поэтому его приходилось «эмулировать» с помощью статических псевдо-конструкторов, которые вызывали функции наподобие init/setup/contruct у создаваемых объектов:
class Vector3
{
var x:Number;
var y:Number;
var z:Number;
function Vector3()
{
init(0, 0, 0);
}
// pseudo-constructor
static function fromComponents(x:Number, y:Number, z:Number)
{
var ret:Vector3 = new Vector3();
ret.init(x, y, z);
return ret;
}
// pseudo-constructor
static function fromVector(Vector3 vec)
{
var ret:Vector3 = new Vector3();
ret.init(vec.X, vec.Y, vec.Z);
return ret;
}
// helper function
function init(x:Number, y:Number, z:Number): void
{
this.x = x;
this.y = y;
this.z = z;
}
}
var v1:Vector3 = new Vector3(); // (0, 0, 0)
var v2:Vector3 = Vector3.fromComponents(1, 2, 3); // (1, 2, 3)
var v3:Vector3 = Vector3.fromVector(v2); // (1, 2, 3)
На этом мы сегодня закончим и, как обычно, в завершении статьи мы сравним описанные сегодня особенности работы с C# и AS3:
|
|
Автор: COOL_ALMANAH