Как я могу создать подкласс, который принимает разные параметры для одного и того же имени функции? - PullRequest
0 голосов
/ 01 ноября 2011

Итак, я сделал этот простой интерфейс:

package{
    public interface GraphADT{
        function addNode(newNode:Node):Boolean;     
    }
}

Я также создал простой класс Graph:

package{

    public class Graph implements GraphADT{

        protected var nodes:LinkedList;

        public function Graph(){
            nodes = new LinkedList();
        }

        public function addNode (newNode:Node):Boolean{
            return nodes.add(newNode);
        }
}

И последнее, но не менее важное: я создал еще один простой класс AdjacancyListGraph:

package{
    public class AdjacancyListGraph extends Graph{

        public function AdjacancyListGraph(){
            super();
        }

        override public function addNode(newNode:AwareNode):Boolean{
            return nodes.add(newNode);
        }
}

Наличие этой настройки приводит к ошибкам, а именно:

1144: Interface method addNode in namespace GraphADT is implemented with an incompatible signature in class AdjacancyListGraph.

При ближайшем рассмотрении стало очевидно, что AS3 не нравятся разные типы параметров из разных классов Graph newNode:Node из Graph и newNode:AwareNode из AdjacancyListGraph

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

Можно ли как-нибудь заставить мой код работать, сохраняя целостность кода?

Ответы [ 3 ]

3 голосов
/ 01 ноября 2011

Простой ответ :

Если вам действительно не нужна ваша функция addNode (), чтобы принимать только AwareNode, вы можете просто изменить тип параметра на Node. Поскольку AwareNode расширяет Node, вы можете без проблем передавать AwareNode. Вы можете проверить правильность типа в теле функции:

subclass... {
    override public function addNode (node:Node ) : Boolean {
        if (node is AwareNode) return nodes.add(node);
        return false;
    }
}

Более длинный ответ :

Я согласен с @ 32bitkid, что вы получаете ошибку, потому что тип параметра, определенный для addNode () в вашем интерфейсе, отличается от типа в вашем подклассе.

Однако основная проблема заключается в том, что ActionScript, как правило, не допускает перегрузки функций (имея более одного метода с одинаковым именем, но с разными параметрами или возвращаемыми значениями), поскольку каждая функция обрабатывается как универсальный член класса - так же, как переменная. Вы можете вызвать такую ​​функцию:

myClass.addNode (node);

но вы также можете назвать это так:

myClass["addNode"](node);

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

Следовательно, вам разрешено переопределять методы только с одной и той же сигнатурой - это способ заставить вас придерживаться того, о чем вы решили, когда писали базовый класс. Хотя вы, очевидно, можете утверждать, что это плохая идея и что имеет больше смысла использовать перегрузку или разрешать разные подписи в подклассах, у способа, которым AS работает с функциями, есть некоторые преимущества, которые в конечном итоге помогут вам решить вашу проблему: можно использовать функцию проверки типа или даже передать ее в качестве параметра!

Учтите это:

class... {

    protected function check (node:Node) : Boolean {
        return node is Node;
    }    

    public function addNode (node:Node) : Boolean {
        if (check(node)) return nodes.add(node);
        return false;
    }
}

В этом примере вы можете переопределить проверку (узел: узел):

subclass... {
    override protected function check (node:Node) : Boolean {
        return node is AwareNode;
    } 
}

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

Вы также можете сделать это еще более динамичным:

class... {
    public function addNode (node:Node, check : Function ) : Boolean {
        if (check(node)) return nodes.add(node);
        return false;
    }
}

Обратите внимание, что эта функция addNode принимает функцию в качестве параметра и что мы вызываем эту функцию вместо метода класса:

var f:Function = function (node:Node) : Boolean {
    return node is AwareNode;
}

addNode (node, f);

Это позволит вам стать очень гибким в своей реализации - вы даже можете проверять достоверность анонимной функции, например, проверять содержимое узла. И вам даже не придется расширять свой класс, если только вы не собираетесь добавлять другие функции, кроме правильности ввода.

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

2 голосов
/ 01 ноября 2011

Полагаю, вопрос на самом деле таков: чего вы пытаетесь достичь?

Что касается того, почему вы получаете ошибку, рассмотрите следующее:

public class AnotherNode extends Node { }

, а затем:

var alGraph:AdjacancyListGraph = new AdjacancyListGraph();

alGraph.addNode(new AnotherNode());  
// Wont work. AnotherNode isn't compatable with the signature
// for addNode(node:AwareNode)

// but what about the contract? 

var igraphADT:GraphADT = GraphADT(alGraph);
igraphADT.addNode(new AnotherNode()); // WTF?

В соответствии с интерфейсом это должно быть хорошо.Но ваша реализация говорит иначе, ваша реализация говорит, что она примет только AwareNode.Существует очевидное несоответствие.Если у вас будет интерфейс, контракт, которому должен следовать ваш объект, то вы можете также следовать ему.Иначе, какой смысл в интерфейсе.

Я утверждаю, что архитектура где-то испортилась, если вы пытаетесь это сделать.Даже если бы язык поддерживал это, я бы сказал, что это «плохая идея ™»

1 голос
/ 01 ноября 2011

Есть более простой способ, чем предложено выше, но менее безопасный:

public class Parent {
public function get foo():Function { return this._foo; }
protected var _foo:Function = function(node:Node):void { ... }}

public class Child extends Parent {
public function Child() {
super();
this._foo = function(node:AnotherNode):void { ... }}}

Конечно, _foo не нужно объявлять на месте, синтаксис используется только для краткости и для демонстрации. Вы потеряете способность компилятора проверять типы, но сопоставление типов во время выполнения все равно будет применяться.

Еще один способ - не объявлять методы в классах, на которых они специализируются, а делать их статическими, тогда вы не будете их наследовать автоматически:

public class Parent {
public static function foo(parent:Parent, node:Node):Function { ... }}

public class Child extends Parent {
public static function foo(parent:Child, node:Node):Function { ... }}

Обратите внимание, что во втором случае защищенные поля доступны внутри статического метода, поэтому вы можете добиться определенной инкапсуляции. Кроме того, если у вас много экземпляров Parent или Child, вы сэкономите на отдельном следе памяти экземпляра (поскольку статические методы, следовательно, static, существует только одна их копия, но методы экземпляра будут скопированы для каждого экземпляра). Недостатком является то, что вы не сможете использовать интерфейсы (может быть улучшением ... зависит от ваших личных предпочтений).

...