Печатание для метода клона, указанного в интерфейсе - PullRequest
0 голосов
/ 30 августа 2009

Я пишу интерфейс, который требует классов для реализации метода clone (). Мой наивный подход к этому был следующим:

public interface ISolvableGame {
    function clone():ISolvableGame;
    //...
}

в другом месте:

public class MyGame implements ISolvableGame {
    public function clone():MyGame {
        // ...
    }
}

Я предполагал, что подпись такого рода будет законной, потому что MyGame.clone() возвращает экземпляр класса, который реализует ISolvableGame, который, как мне кажется, удовлетворяет контракту в интерфейсе. Однако код, подобный приведенному выше, генерирует ошибку компиляции, ссылаясь на тот факт, что MyGame.clone() имеет сигнатуру, отличную от той, которая указана в интерфейсе.

Таким образом, мой вопрос: как мне создать интерфейс, требующий клонирования, если реализованный метод должен точно соответствовать сигнатуре в интерфейсе? Очевидно, что делать интерфейс более конкретным не имеет никакого смысла. Но если бы я сделал реализованный метод менее конкретным (т. Е. Если бы я набрал MyGame.clone() как возвращающий ISolvableGame), другие пользователи этого метода клона больше не будут знать, что они получают.

Нужны ли мне две версии метода clone, одна из которых напечатана как ISolvableGame для соответствия интерфейсу, а другая - как MyGame для использования в классе? Или есть лучший подход?


Примечание: Я работаю в ActionScript3 (Java-подобный язык, который реализует спецификацию ECMA4). Я отметил это как независимый от языка в предположении, что AS3 не уникален в том, как он обрабатывает интерфейсы. Но если приведенный выше пример кода будет работать на других языках, моя проблема может быть связана с моим языком.


Обновление: Мне пришло в голову выяснить, как с этим справляются основные библиотеки моего языка. Например, существует интерфейс IEventDispatcher, который определяет метод dispatch():Event - так что любой класс, отправляющий подкласс из Event, не может реализовать IEventDispatcher, что в конечном итоге похоже на мою проблему.

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

Я думаю, что мой выбор:

  • В конечном счете, полагайтесь на наследование, как это делают основные библиотеки
  • Реализовать два метода, как описывает Фредерик, с разными именами
  • Пожертвуйте безопасность во время компиляции, как описывает Джеймс

Ответ: В конце концов, я остановился на опции, позволяющей интерфейсу указывать метод cloneToSolvable - то есть интерфейс определяет метод для клонирования типа интерфейса, и классы реализации должны иметь этот метод в дополнение к любому более конкретно типизированному методу клонирования, который у них может быть. Это показалось мне наименее неприятным из вариантов.

Ответы [ 3 ]

1 голос
/ 30 августа 2009

AS3 не позволяет выполнять перегрузки (более чем одна функция с одинаковым именем, различаемая типами возвращаемых данных или типами параметров), только переопределяет (подклассы могут заменить реализации базового класса).

, например

function foo():int {}
function foo():String {}
function foo(a:String):void {}

Перегружаются функции с именем foo - вы не можете сделать это в ActionScript. Интерфейсы действительно ближе к переопределениям, чем к перегрузкам, так как вы в некотором смысле «наследуете» интерфейс.

// in your specific case you couldn't have these two functions
// defined within the same scope
public function clone():MyGame {}
public function clone():ISolvableGame {}

То, что вы пытаетесь сделать, - это смешать две концепции. Вы хотите перегрузить интерфейс, который пытаетесь переопределить. Как показывает пост Фредрика, даже на языках, которые поддерживают перегрузку (из которых AS3 не является одним), вы часто не можете делать оба одновременно. Интерфейс вынуждает вас иметь функцию с точной подписью: clone():ISolvableGame, и вы не можете перегрузить ее дополнительным clone():MyGame

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

, например

// this will throw if clone does not return an
// object that is cast-able to MyGame
var game:MyGame = MyGame(someObj.clone());

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

1 голос
/ 30 августа 2009

У меня нет опыта работы с ActionScript, поэтому это может быть или не быть работоспособным решением, но в C # я обычно делаю так:

public class MyGame : ISolvableGame {
    public MyGame clone(){
        // ...
    }

    ISolvableGame ISolvableGame.clone() {
        return this.clone();
    }
}

Другими словами, я создаю «типизированный» метод клонирования (который не реализует интерфейс, поскольку он отличается от возвращаемого типа). Затем я делаю явную реализацию метода интерфейса, который вызовет типизированный метод клона и вернет результат. Это законный ход, поскольку MyGame можно разыграть до ISolvableGame.

0 голосов
/ 01 сентября 2009

Унаследованные методы-клоны всегда имеют суперкласс в качестве возвращаемого типа. Например, все классы событий переопределяют метод clone класса flash.events.Event, который возвращает объект Event, и реализуют его для возврата соответствующего объекта производного класса.

...