поиск

Классовые оборотни

Намусорил: Алексей «Vooparker» Аникутин
В категории: ActionScript 3

Не секрет, что ActionScript 3 не поддерживает перегрузки методов. Иногда это может привести к затруднительным ситуациям. Например, у нас есть два интрефейса, в которых объявлены одноименные методы, но с различной сигнатурой, и требуется создать класс, который должен реализовывать оба этих интерфейса. При попытке реализовать классом оба таких интерфейса мы совершенно законно столкнемся с ошибкой. Чтобы быть более конкретным приведу пример.

У нас есть два интерфейса IHuman и IMonster, в которых объявлен метод kill, только в первом случае метод в качестве аргумента принимает IMonster-жертву, а во втором — в роли жертвы должен выступать IHuman. При попытке создать класс Werewolf, который должен являться как и IHuman, так и IMonster мы окажемся в затруднительной ситуации, поскольку как я уже сказал ранее ActionScript 3 не поддерживает перегрузки методов.

В сложившейся ситуации у нас есть два варианта наших действий. Первый вариант предполагает изменение условий задачи, проще говоря мы можем переименовать методы, например killMonster для IHuman и killHuman для IMonster. Если у вас есть возможность пойти по этому пути, то можете не колебаться и смело выбирать его. Второй вариант, исходит из того, что вы не можете изменить имена методов. В этом случае, можно создать два отдельных класса, каждый из которых реализует необходимый интерфейс и использовать их вместе в составе третьего класса Werewolf. Здесь есть маленькая тонкость. Как между двумя этими классами расшарить внутреннюю логику Werewolf. Например, в методе kill этих классов вызвать приватный метод prepareToKill класса Werewolf. В этой ситуации я для себя нашел небольшой workaround — создание класса оборотня.

Для начала нам потребуются выше обзначенные интерфейсы.

Actionscript:
  1. package
  2. {
  3.     public interface IHuman
  4.     {
  5.         function kill (victim:IMonster):void;
  6.     }
  7. }

Actionscript:
  1. package
  2. {
  3.     public interface IMonster
  4.     {
  5.         function kill (victim:IHuman):void;
  6.     }
  7. }

Переходим к созданию класса Werewolf, содержащий в себе как класс реализующий IHuman, так и класс реализующий IMonster. Оба эти классы возможны только как составляющие Werewolf, соответственно их можно смело сделать вложенными, то есть объявить за скобками пакета в том же файле, что описывает собственно сам класс Werewolf.

Actionscript:
  1. package
  2. {
  3.     public class Werewolf
  4.     {
  5.         private var _monster:IMonster = null;
  6.         private var _human:IHuman = null;
  7.        
  8.         public function Werewolf()
  9.         {
  10.             _monster = new Monster();
  11.             _human = new Human();
  12.         }
  13.        
  14.         public function asHuman ():IHuman
  15.         {
  16.             return _human;
  17.         }
  18.        
  19.         public function asMonster ():IMonster
  20.         {
  21.             return _monster;
  22.         }
  23.     }
  24. }
  25.  
  26.  
  27. internal class Monster implements IMonster
  28. {   
  29.     public function Monster ()
  30.     {
  31.     }
  32.    
  33.     public function kill (victim:IHuman):void
  34.     {
  35.         trace("human killed");
  36.     }
  37. }
  38.  
  39. internal class Human implements IHuman
  40. {
  41.     public function Human ()
  42.     {
  43.     }
  44.    
  45.     public function kill (victim:IMonster):void
  46.     {
  47.         trace("monster killed");
  48.     }
  49. }

И так мы имеем класс Werewolf, который может быть использован как IMonster, так и IHuman, для этого необходимо вызвать соответсвующий метод. Теперь вернемся к условиям, а именно, кроме реализации классов Monster и Human, нам требуется расшарить для них внутреннюю логику Werewolf. Так как эти классы могут существовать только в составе Werewolf мы могли бы смело дать им возможность вызывать приватные методы Werewolf. Однако, как мы знаем приватный метод, он на то и приватный, чтобы быть доступным только в рамках своего класса. Но нам никто не мешает создать собственную область видимости. А помогут нам в этом пространтсва имен. Чтобы создать неймспейс, видимый только в пределах наших классов, объявим его как и классы Monster и Human вне пакета. Таким образом методы, объявленные в этом неймспейсе, могут быть вызваны, любым из наших трех классов.

Actionscript:
  1. package
  2. {
  3.     public class Werewolf implements IWerewolf
  4.     {
  5.         private var _monster:IMonster = null;
  6.         private var _human:IHuman = null;
  7.        
  8.         public function Werewolf()
  9.         {
  10.             super();
  11.             _monster = new Monster(this);
  12.             _human = new Human(this);
  13.         }
  14.        
  15.         werewolf_private function prepareToKill ():void
  16.         {
  17.             trace("prepared for kill");
  18.         }
  19.        
  20.         public function asHuman ():IHuman
  21.         {
  22.             return _human;
  23.         }
  24.        
  25.         public function asMonster ():IMonster
  26.         {
  27.             return _monster;
  28.         }
  29.     }
  30. }
  31.  
  32. internal namespace werewolf_private;
  33.  
  34. internal class Monster implements IMonster
  35. {
  36.     private var _owner:Werewolf = null;
  37.    
  38.     public function Monster (owner:Werewolf)
  39.     {
  40.         _owner = owner;
  41.     }
  42.    
  43.     public function kill (victim:IHuman):void
  44.     {
  45.         _owner.werewolf_private::prepareToKill();
  46.         trace("human killed");
  47.     }
  48. }
  49.  
  50. internal class Human implements IHuman
  51. {
  52.     private var _owner:Werewolf = null;
  53.    
  54.     public function Human (owner:Werewolf)
  55.     {
  56.         _owner = owner;
  57.     }
  58.    
  59.     public function kill (victim:IMonster):void
  60.     {
  61.         _owner.werewolf_private::prepareToKill();
  62.         trace("monster killed");
  63.     }
  64. }

Таким образом, мы получили метод prepareToKill видимый только в пределах наших классов. Аналогичным образом можно расшарить и прочую логику. На этом можно было бы и остановиться, но есть один момент, каждая из составляющих как Human, так и Monster не являются Werewolf-ом. Решается это достаточно просто. Введем интерфейс IWerewolf, где и обозначим соответствующий API.

Actionscript:
  1. package
  2. {
  3.     public interface IWerewolf
  4.     {
  5.         function asHuman ():IHuman;
  6.        
  7.         function asMonster ():IMonster;
  8.     }
  9. }

Теперь остается только реализовать этот интерфейс всеми тремя классами.

Actionscript:
  1. package
  2. {
  3.     public class Werewolf implements IWerewolf
  4.     {
  5.         private var _monster:IMonster = null;
  6.         private var _human:IHuman = null;
  7.        
  8.         public function Werewolf()
  9.         {
  10.             super();
  11.             _monster = new Monster(this);
  12.             _human = new Human(this);
  13.         }
  14.        
  15.         werewolf_private function prepareToKill ():void
  16.         {
  17.             trace("prepared for kill");
  18.         }
  19.        
  20.         public function asHuman ():IHuman
  21.         {
  22.             return _human;
  23.         }
  24.        
  25.         public function asMonster ():IMonster
  26.         {
  27.             return _monster;
  28.         }
  29.     }
  30. }
  31.  
  32. internal namespace werewolf_private;
  33.  
  34. internal class Monster implements IMonster, IWerewolf
  35. {
  36.     private var _owner:Werewolf = null;
  37.    
  38.     public function Monster (owner:Werewolf)
  39.     {
  40.         _owner = owner;
  41.     }
  42.    
  43.     public function kill (victim:IHuman):void
  44.     {
  45.         _owner.werewolf_private::prepareToKill();
  46.         trace("human killed");
  47.     }
  48.    
  49.     public function asHuman ():IHuman
  50.     {
  51.         return _owner.asHuman();
  52.     }
  53.    
  54.     public function asMonster ():IMonster
  55.     {
  56.         return this;
  57.     }
  58. }
  59.  
  60. internal class Human implements IHuman, IWerewolf
  61. {
  62.     private var _owner:Werewolf = null;
  63.    
  64.     public function Human (owner:Werewolf)
  65.     {
  66.         _owner = owner;
  67.     }
  68.    
  69.     public function kill (victim:IMonster):void
  70.     {
  71.         _owner.werewolf_private::prepareToKill();
  72.         trace("monster killed");
  73.     }
  74.    
  75.     public function asHuman ():IHuman
  76.     {
  77.         return this;
  78.     }
  79.    
  80.     public function asMonster ():IMonster
  81.     {
  82.         return _owner.asMonster();
  83.     }
  84. }

Проверим что у нас получилось.

Actionscript:
  1. package
  2. {
  3.     import flash.display.Sprite;
  4.  
  5.     public class World extends Sprite
  6.     {
  7.         public function World()
  8.         {
  9.             var werewolf:Werewolf = new Werewolf();
  10.            
  11.             // "Кастим" werewolf как IMonster и как IHuman
  12.             trace(werewolf.asMonster() is IMonster);
  13.             trace(werewolf.asHuman() is IHuman);
  14.            
  15.             // Проверяем является ли каждая составляющая IWerewolf
  16.             trace(werewolf.asMonster() is IWerewolf);
  17.             trace(werewolf.asHuman() is IWerewolf);
  18.            
  19.             // Ну а здесь уже мистика :)
  20.             trace((werewolf.asMonster() as IWerewolf).asHuman() is IHuman);
  21.             trace((werewolf.asHuman() as IWerewolf).asMonster() is IMonster);
  22.            
  23.             // Попробуем убить нашим оборотнем как человека так и монстра
  24.             var knight:Knight = new Knight();
  25.             var zombie:Zombie = new Zombie();
  26.            
  27.             werewolf.asMonster().kill(knight);
  28.             werewolf.asHuman().kill(zombie);
  29.         }
  30.     }
  31. }

Напоследок напомню, при возможности изменить условия, а именно устранить конфликт путем смены сигнатуры методов, не раздумывайте и выбирайте этот вариант. Описанный прием является скорее крайней мерой и способом сделать код менее прозрачным.



Kомментариев - 14 к «Классовые оборотни»

magius [7 июля, 2008 в 05:11]

поскольку в результате Werewolf не является ни IHuman, ни IMonster, непонятно какая задача вообще решалась.

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

правильным решением было бы создание интерфейсов ICanBeKilled и ICanKill

Cemaprjl [7 июля, 2008 в 05:58]

про возможность изменения неймспеса занятно, однако все это попахивает плясками с бубном, либо пример неудачный
в данном контексте ИМХО бюлее уместно использовать нечто вроде:
package
{
public interface ICharacter
{
function move():void;
function talk():void;
}

public interface IKillerChar
{
function kill(char:ICharacter):void;
}
}
так мы избавимся от необходимости перегружать метод kill(), и интерфейсы разделим согласно тому что классы могут делать а не кем являются. А если вам понадобится персонажу разрешить убивать себе подобных, даже если и не понадобится то такой вариант проще и понятнее.

Vooparker [7 июля, 2008 в 07:48]

> поскольку в результате Werewolf не является ни IHuman, ни IMonster, непонятно какая задача вообще решалась.

Он и не может являться, но может сыграть их роль. Поскольку для этого требуется создать два класса,
задача в этом случае - между двумя этими классами расшарить внутреннюю логику Werewolf, при этом не дать возможности существовать отдельным ипостасиям самостоятельно.

Примеденный пример не взят из жизни, а немного надуман. А сама ситуация, как я может не совсем акцентировал, есть следствие непродуманного проектирования. Это все так и понятно, но бывают такие ситуации когда с кодом ты вынужден работать как с константой, то есть нет возможности, как я говорил уже выше изменить условий, и в этом случае описанный прием имеет право на существовани и "является скорее крайней мерой и способом сделать код менее прозрачным".

magius [7 июля, 2008 в 08:14]

в данном случае вы имеете дело не с кодом, а с дефектом, который надо исправлять.

имхо ваш пост - про то, как вы вставили куда-то не самый удачный костыль.
существовать он имеет право ровно до исправления дефекта и не секундой дольше.

Vooparker [7 июля, 2008 в 08:32]

Вопрос лишь в том, есть ли у вас возможность исправить этот дефект, и каковы затраты на его исправления, и главное, будут ли затраты адекватны полученным результатам.

Что касается костыля, все очень просто. Я столкнулся со схожей ситуацией, да это был "дефект" и пошел я не пути описанному в выше, а в моем случае через создание базового интерфейса. То что я описал в статье, стало плодом размышлений после. Собственно ими я и поделился, без всяких претензий на "правильность", более того, предостерегая от использования их в реальной практике. Так что, мне все таки лучше знать, про что мой пост )

magius [7 июля, 2008 в 09:06]

а можно поподробнее про "базовый интерфейс"?
то есть доступ к сорцам интерфейсов все же был? :) что же тогда мешало провести нормальную реорганизацию кода, вместо выстраивания стройной системы костылей и подпорок? :)

Vooparker [7 июля, 2008 в 09:16]

> что же тогда мешало провести нормальную реорганизацию кода
ничего не мешало и это было сделано. А то что я описал, как уже было сказано, "стало плодом размышлений после".

Slon_vsapogah [7 июля, 2008 в 23:40]

Прикольно! Много серьезного кода, когда удасться применить - хер знает, но что-то в этом есть :))

P.S: Неймспейсы я так и не знаю (увы)
P.P.S: А регулярки все-таки узнал и применил очень жестко :)

маничит [11 июля, 2008 в 16:13]

Классная возможность, хотя пока мне до такого далеко...

Blaybox [15 июля, 2008 в 11:02]

Ничего Манчит, учись, эксперементируй и у тебя все получится!

Serch [3 августа, 2008 в 11:10]

давно было пора провести реорганизацию

Slon_vsapogah [7 августа, 2008 в 09:59]

Спамеры, сука, хитрые стали!

Chay [13 августа, 2008 в 21:34]

Как это ActionScript 3 не поддерживает перегрузки методов? А выражение override Вам ничего не говорит?
override public function method():void {
// всё перегружается замечательно
}

Юрий Яровой [14 августа, 2008 в 10:02]

Slon_vsapogah, это точно.
Chay, не путай перегрузку метода с переопределением.

Написать комментарий:

 

Bы можете использовать следующие теги для форматирования: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



User's collector

Внимание!
Эта опция станет доступной только после того как вы авторизуетесь.


 запомнить меня 
Я новый пользователь

На правах рекламы