Pull to refresh

С# для AS3 разработчиков. Часть 4: Абстрактные классы и функции

Reading time 5 min
Views 4.9K
Original author: Jackson Dunstan
image

Перевод статьи From AS3 to C#, Part 4: Abstract Classes and Functions

В этой статье мы наконец-то начнём разбираться в нюансах C#, аналогов которых нет в AS3. И первым делом мы рассмотрим абстрактные классы и функции. В AS3 необходимо было придумывать обходные пути, чтобы они работали правильно на этапе исполнения (run-time). Но C# предоставляет возможность заставить их работать на этапе компиляции (compile-time), и сегодня мы разберём эти способы.


Статические инициализаторы

Но, до этого, я бы хотел рассказать об одной особенности AS3 классов, о которой я забыл рассказать в предыдущих статьях: статические инициализаторы (static initializers), так же известные как инициализаторы класса, конструкторы класса или статические конструкторы. Это – функция, которая будет вызвана автоматически, когда статические поля класса должны быть инициализированы. Вот, как это выглядело в AS3:

class Person
{
    private static var NEXT_ID:int;
    private var id:int;
    // static initializer:
    {
        NEXT_ID = 1;
    }
    // instance constructor
    function Person()
    {
        id = NEXT_ID++;
    }
}


Статические инициализаторы используются не часто, т.к. у нас есть возможность объявлять и инициализировать поля в одно и то же время. Например:

private static var NEXT_ID:int = 1;


Но, они могут быть полезны, если необходимо реализовать более сложную логику поведения приложения. В любом случае, вот, как это может быть реализовано в C#:

class Person
{
    private static int NextID;
    private int id;
    // static initializer:
    static Person()
    {
        NextID = 1;
    }
    // instance constructor
    Person()
    {
        id = NextID++;
    }
}


Статические инициализаторы в C# называются “статическими конструкторами” и работают по аналогии с обычными конструкторами, но не для отдельных экземпляров классов, а для всего класса целиком. Синтаксис подобных конструкторов совпадает с обычными, но в начале объявления конструктора добавляется ключевое слово static. У данных конструкторов не может быть модификаторов доступа (private, public и т.п.) и они не могут принимать входящие параметры.

Абстрактные классы

А теперь, давайте поговорим об абстрактных классах: это – такие классы, которые не могут быть инстанциированы напрямую. Чтобы создать экземпляр абстрактного класса, вам необходимо будет создать не абстрактный класс, который будет наследоваться от абстрактного, и инстанциировать этот не абстрактный класс. По-умолчанию в AS3 нет подобного функционала на этапе компиляции, но, существует довольно популярный способ обойти это ограничение:

class ExtrudedShape
{
    private var depth:int;
    protected static const HIDDEN_KEY:Object = {};
    function ExtrudedShape(ABSTRACT:Object, depth:int)
    {
        if (ABSTRACT != HIDDEN_KEY)
        {
            throw new ArgumentError("ExtrudedShape is an abstract class");
        }
        this.depth = depth;
    }
    function get area(): int
    {
        return 0;
    }
    function get volume(): int
    {
        return depth * area;
    }
}


В данном случае создание ExtrudedShape напрямую всё ещё возможно, и подобный код будет компилироваться:

var shape:ExtrudedShape = new ExtrudedShape(null, 3);


Но на этапе исполнения сработает проверка первого аргумента, что повлечёт за собой появление ошибки ArgumentError, и экземпляр ExtrudedShape не будет создан. Это произойдёт из-за того, что классы, не унаследованные от ExtrudedShape не будут иметь доступа к protected константе HIDDEN_KEY, но, в то же время, классы-производные от ExtrudedShape смогут обращаться к этой переменной для передачи в родительский конструктор:

class ExtrudedCircle extends ExtrudedShape
{
    function ExtrudedCircle(depth:int)
    {
        super(HIDDEN_KEY, depth);
    }
}


Это – довольно эффективный способ реализации абстрактных классов на этапе проигрывания, но C# предоставляет возможность сделать всю работу на этапе компиляции:

abstract class ExtrudedShape
{
    private int depth { get; private set; }
    ExtrudedShape(int depth)
    {
        this.depth = depth;
    }
    int Area
    {
        get { return 0; }
    }
    int Volume
    {
        get { return depth * Area; }
    }
}


Обратите внимание на использование ключевого слова abstract вначале класса. Оно означает, что компилятор не должен разрешать создание данного класса напрямую. Данный подход не требует дополнительного кода или “обходных путей”, которые необходимы в AS3 (производным классам не нужно использовать HIDDEN_KEY, а их инициализация и объявление выглядит точно так же, как и у других классов):

class ExtrudedCircle : ExtrudedShape
{
    ExtrudedCircle(int depth)
        : base(depth)
    {
    }
}


Абстрактные функции

Абстрактные функции используются в тех случаях, когда необходимо указать, что реализация определённой функции, обязательно должна быть переопределена в дочернем классе. И снова, в AS3 нет возможности реализовать подобное на этапе компиляции, но, как и в случае с абстрактными классами, существует способ обойти это ограничение:

class ExtrudedShape
{
    private var depth:int;
    protected static const HIDDEN_KEY:Object = {};
    function ExtrudedShape(ABSTRACT:Object, depth:int)
    {
        if (ABSTRACT != HIDDEN_KEY)
        {
            throw new ArgumentError("ExtrudedShape is an abstract class");
        }
        this.depth = depth;
    }
    function get area(): int
    {
        throw new Error("'get area' is an abstract function");
        return 0;
    }
    function get volume(): int
    {
        return depth * area;
    }
}


В данном примере класс ExtrudedShape не реализует функционал функции get area, так как он ничего не знает о ней. В данной версии, обращение к функции get area класса ExtrudedShape вызовет ошибку. Данный подход позволяет реализовать абстрактные функции на этапе воспроизведения, но не на этапе компиляции. Например, следующий код будет успешно компилироваться без реализации функции get area:

class ExtrudedCircle extends ExtrudedShape
{
}


Вместо этого, в C# мы можем просто использовать ключевое слово abstract:

abstract class ExtrudedShape
{
    private int depth { get; private set; }
    ExtrudedShape(int depth)
    {
        this.depth = depth;
    }
    abstract public int Area
    {
        get;
    }
    int Volume
    {
        get { return depth * Area; }
    }
}
class ExtrudedCircle : ExtrudedShape
{
    private int area;
    override int Area
    {
        get { return area; }
    }
}


Это же ключевое слово будет использоваться для обычных функций (не геттер/сеттер):

abstract class GameEntity
{
    abstract void TakeDamage(int damage);
}
class Enemy : GameEntity
{
    int health;
    override void TakeDamage(int damage)
    {
        health -= damage;
    }
}


Сегодня мы обсудили абстрактные классы и функции, а так же статические инициализаторы. Для закрепления, давайте сравним особенности реализации этого функционала в C# и AS3:

////////
// C# //
////////
// Abstract class
abstract class GameEntity
{
    private static int NextID;
    protected int health;
    int id;
    static GameEntity()
    {
        NextID = 1;
    }
    GameEntity(int health)
    {
        this.health = health;
        this.id = NextID++;
    }
    // Abstract property
    bool Friendly
    {
        abstract get;
    }
    // Abstract function
    abstract void TakeDamage(int amount)
    {
    }
}
// Non-abstract ("concrete") class
class Enemy : GameEntity
{
    Enemy(int health)
        : base(health)
    {
    }
    // Implemented abstract property
    override bool Friendly
    {
        get { return false; }
    }
    // Implemented abstract function
    override void TakeDamage(int amount)
    {
        health -= amount;
    }
}

/////////
// AS3 //
/////////
// Abstract class - only enforced at run-time
class GameEntity
{
    private static var NEXT_ID:int;
    protected static const HIDDEN_KEY:Object = {};
    protected var health:int;
    var id:int;
    // Static initializer
    {
        NEXT_ID = 1;
    }
    function GameEntity(ABSTRACT:Object, health:int)
    {
        if (ABSTRACT != HIDDEN_KEY)
        {
            throw new ArgumentError("GameEntity is abstract");
        }
        this.health = health;
        this.id = NEXT_ID++;
    }
    // Abstract property/getter - only enforced at run-time
    function get friendly(): Boolean
    {
        throw new Error("'get friendly' is abstract");
        return false;
    }
    // Abstract function - only enforced at run-time
    function takeDamage(amount:int): void
    {
        throw new Error("takeDamage is abstract");
    }
}
// Non-abstract ("concrete") class
class Enemy extends GameEntity
{
    function Enemy(health:int)
    {
        super(HIDDEN_KEY, health);
    }
    // Implemented abstract property
    override function get friendly(): Boolean
    {
        return false;
    }
    // Implemented abstract function
    override function takeDamage(amount:int): void
    {
        health -= amount;
    }
}



В следующей статье мы поговорим о деструкторах, трюках при работе с перегрузкой конструкторов и о многом другом.

Оставайтесь с нами!
Tags:
Hubs:
+8
Comments 2
Comments Comments 2

Articles