Проблема с супер и переопределением - лучший способ сделать это? - PullRequest
3 голосов
/ 11 июля 2011

У меня проблема с использованием super и overriding.По сути, класс B, который расширяет A, имеет установщик текущего состояния класса.Внутри установщика, в зависимости от значения текущего состояния, может выполняться другое событие.

В установщике для B первое, что происходит, это то, что он вызывает super, так что установщик дляA может пойти запустить общие события.Затем элемент управления возвращается к установщику B, где я могу при необходимости выполнить определенные события.

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

Следующий код иллюстрирует то, о чем я говорю (это классно, но не имеет значения):

class A
{
    public int num = 0;
    public void setFoo( int i ) 
    { 
        println "A: $i"; 
        num = i + 1;

        // what's actually happening, is that in the setter, depending
        // on the value, an event can be executed, which will in turn
        // call setFoo() on the class. this is just the equivalet
        if( i < 3 )
            this.setFoo( num );
    }
}
class B extends A
{
    public void setFoo( int i ) 
    {
        println "B: $i - num $num";
        super.setFoo( i );
        println "After super for $i - num: $num";
    }
}

def B = new B();
B.foo = 0;

В результате получается:

B: 0 - num 0
A: 0
B: 1 - num 1
A: 1
B: 2 - num 2
A: 2
B: 3 - num 3
A: 3
After super for 3 - num: 4
After super for 2 - num: 4
After super for 1 - num: 4
After super for 0 - num: 4

Когда я возвращаюсь к B после вызова super («После супер для ...»), значение num всегда одинаково, что означает, что он привинчивает к тому, что япытается сделать в B (то есть запускать определенные события).

Некоторые пункты архитектуры для начала:

  • "Почему бы не использовать i вместо numв сеттер для B "?- Это просто самый простой пример, чтобы показать проблему - то, что на самом деле происходит в моем коде, отличается, просто та же самая проблема.В моем случае у меня есть доступ к num, а не i.Даже если я переписал часть этого кода для передачи i, состояние класса будет изменено (из-за базового класса)
  • Это серверная среда, поэтому у меня нет доступа к кадрупетля или что-то подобное.Это основано на событиях.
  • Должна быть возможность асинхронно выполнить событие или настроить событие на более позднее расписание, но для этого требуется много предварительных знаний о том, где и когда будет использоваться событие, котороев первую очередь разбивает весь смысл событий

Мне нужен способ запуска событий, основанный на состоянии класса, но произойдет ли это после возврата из super(все еще работая для базового класса), если это имеет какой-то смысл.

Идеи?

РЕДАКТИРОВАТЬ

Чтобы дать лучшее представление о кодеЯ использую (основываясь на предложении Дона использовать обратный вызов), вот упрощенная версия того, что у меня есть.(Если вы хотите запустить его, вы можете просто скопировать его в http://groovyconsole.appspot.com/):

// A is our base class
​class A{
    public int currentState= 0;
    public void setCurrentState( int i ) 
    { 
        this.currentState = i;
        this._onStateChanged();
    }

    protected void _onStateChanged()
    {
        println "The state in A is $currentState";

        // depending on the state launch some events.
        // these can changed the current state of
        // B
        if( this.currentState == 0 )
        {
            def event = new MyEvent( this );
            event.execute();
        }
    }
}

// B is a more specific version of A
class B extends A
{
    protected void _onStateChanged()
    {
        println "The state in B is $currentState";
        super._onStateChanged();
        println "The state in B afterwards is $currentState";

        // launch specific events based on the current state
        if( this.currentState == 0 )
           println "Launch a specific event!";
    }
}

// simple event class that can change the status of B
class MyEvent
{
    private B b = null;
    public MyEvent( B b )
    {
        this.b = b;
    }
    public void execute()
    {
        // do some stuff
        b.currentState++;
    }
}

// program start
def b = new B();
b.currentState = 0;​

B должен вызвать super, так как есть некоторые состояния, где я хочу базовый плюс конкретное событие. Базовые события обычно используются для установки состояния программы, в то время как конкретные реагируют на них.

В этом примере мой вывод:

The state in B is 0
The state in A is 0
The state in B is 1
The state in A is 1
The state in B afterwards is 1
The state in B afterwards is 1

т.е. Bникогда не реагирует на состояние 0

Edit

Если я изменю вызов super() в B на конец _onStateChanged(), а не наначать, это даст ему возможность среагировать на состояние до того, как оно будет изменено. Это простое решение этой проблемы или просто неправильно?

Редактировать Итак, я придумалэто (опять же, вы можете скопировать его на сайт приложения Groovy для консоли):

// A is our base class
class A{
    public int currentState = 0;
    public int nextState = 0;
    public boolean canChange = true;
    public void setCurrentState( int i ) 
    { 
        if( this.canChange )
        {
            this.currentState = i;
            this._onStateChanged();
        }
        else
            this.nextState = i;
    }

    protected void _onStateChanged()
    {
        println "The state in A is $currentState";

        // depending on the state launch some events.
        // these can changed the current state of
        // B
        if( this.currentState == 0 )
        {
            def event = new MyEvent( this );
            event.execute();
        }
    }
}

// B is a more specific version of A
class B extends A
{
    protected void _onStateChanged()
    {
        this.canChange = false;
        println "The state in B is $currentState";
        super._onStateChanged();
        println "The state in B afterwards is $currentState";

        // launch specific events based on the current state
        if( this.currentState == 0 )
           println "Launch a specific event!";

        this.canChange = true;
        if( this.nextState != 0 )
        {
            int state = this.nextState;
            this.nextState = 0;
            this.currentState = state;
        }
    }
}

// simple event class that can change the status of B
class MyEvent
{
    private B b = null;
    public MyEvent( B b )
    {
        this.b = b;
    }
    public void execute()
    {
        // do some stuff
        b.currentState++;
    }
}

// program start
def b = new B();
b.currentState = 0;​

Это дает мне желаемый результат:

The state in B is 0
The state in A is 0
The state in B afterwards is 0
Launch a specific event!
The state in B is 1
The state in A is 1
The state in B afterwards is 1

но это уродливо. Лучше?

Ответы [ 5 ]

2 голосов
/ 11 июля 2011

Принципиально, A.setFoo сломан

class A
{
    public int num = 0;
    public void setFoo( int i ) 
    { 
        println "A: $i"; 
        num = i + 1;

        // what's actually happening, is that in the setter, depending
        // on the value, an event can be executed, which will in turn
        // call setFoo() on the class. this is just the equivalet
        if( i < 3 )
            this.setFoo( num );
    }
}

Потому что new A().setFoo(2) (например) вызовет переполнение стека. Что-то вроде следующего может быть лучше дизайн

abstract class A
{
    public int num = 0;

    abstract void setFooCallback(int i)

    public final void setFoo( int i ) 
    { 
        println "A: $i"; 
        num = i + 1;

        // what's actually happening, is that in the setter, depending
        // on the value, an event can be executed, which will in turn
        // call setFoo() on the class. this is just the equivalet
        if( i < 3 )
            this.setFooCallback( num );
    }
}

class B extends A
{
    public void setFooCallback( int i ) 
    {
        // Implement me to launch custom events or whatever
        // If you call setFoo in here you'll get a stack overflow
    }
}

Если вам нужно создать экземпляры A, просто удалите модификаторы abstract и измените setFooCallback на:

void setFooCallback(int i) { // default implementation does nothing }

К вашему сведению, выше приведен пример шаблона (метода)

0 голосов
/ 15 сентября 2011

Для меня похоже, что вы пытаетесь выполнить определенные B события перед событиями по умолчанию A. В этом случае наиболее подходящим для меня подходом будет сначала выполнить B события, а затем вызвать super для запуска A событий.

UPDATE:

Использовать шаблон шаблона:

​class A{
public int currentState= 0;
public void setCurrentState( int i ) 
{ 
    this.currentState = i;
    println "The state in A is $currentState";

    // Maybe you need to split your MyEvent into two events.
    // MyEventA - Does whatever required before executing special events.
    def eventA = new MyEventA( this );
    eventA.execute();

    this._invokeSpecialEvents();

    // depending on the state launch some events.
    if( this.currentState == 0 )
    {
        // MyEventB - Does whatever required after executing special events (do actual currentState change).
        def event = new MyEventB( this );
        event.execute();
    }
}

protected void _invokeSpecialEvents()
{
    // You mentioned that cannot make A class abstract.
    // This method is empty rather than abstract only for that reason.
}

}

0 голосов
/ 15 сентября 2011

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

// A is our base class
class A{
    public int currentState = 0;
    public int nextState = 0;
    public boolean canChange = true;
    public void setCurrentState( int i ) 
    { 
        if( this.canChange )
        {
            this.currentState = i;
            this._onStateChanged();
        }
        else
            this.nextState = i;
    }

    protected void _onStateChanged()
    {
        println "The state in A is $currentState";

        // depending on the state launch some events.
        // these can changed the current state of
        // B
        if( this.currentState == 0 )
        {
            def event = new MyEvent( this );
            event.execute();
        }
    }
}

// B is a more specific version of A
class B extends A
{
    protected void _onStateChanged()
    {
        this.canChange = false;
        println "The state in B is $currentState";
        super._onStateChanged();
        println "The state in B afterwards is $currentState";

        // launch specific events based on the current state
        if( this.currentState == 0 )
           println "Launch a specific event!";

        this.canChange = true;
        if( this.nextState != 0 )
        {
            int state = this.nextState;
            this.nextState = 0;
            this.currentState = state;
        }
    }
}

// simple event class that can change the status of B
class MyEvent
{
    private B b = null;
    public MyEvent( B b )
    {
        this.b = b;
    }
    public void execute()
    {
        // do some stuff
        b.currentState++;
    }
}

// program start
def b = new B();
b.currentState = 0;​

Второе решение требует подхода, более подходящего для слушателя. И базовый, и расширяющий регистр функций класса для вызова при изменении состояния:

// A is our base class
class A{
    public int currentState = 0;
    public def listeners = [];
    public void setCurrentState( int i ) 
    { 
        // call each of our listeners with the current state
        this.currentState = i;
        listeners.each { it( i ); }
    }

    public A()
    {
        this.addListener( this.&_onStateChanged );
    }

    public void addListener( def callback )
    {
        this.listeners.add( 0, callback );
    }

    protected void _onStateChanged( int state )
    {
        println "The state in A is $state";

        // depending on the state launch some events.
        // these can changed the current state of
        // B
        if( state == 0 || state == 1 )
        {
            def event = new MyEvent( this );
            event.execute();
        }
    }
}

// B is a more specific version of A
class B extends A
{
    public B()
    {
        super();
        this.addListener( this.&_onBStateChanged );
    }

    protected void _onBStateChanged( int state )
    {
        println "The state in B is $state";

        // launch specific events based on the current state
        if( state == 0 )
            println "Launch a specific event!";
    }
}

// simple event class that can change the status of B
class MyEvent
{
    private B b = null;
    public MyEvent( B b )
    {
        this.b = b;
    }
    public void execute()
    {
        // do some stuff
        b.currentState++;
    }
}

// program start
def b = new B();
b.currentState = 0;

Оба дают мне вывод, который я ищу, хотя второй немного нарушен, поскольку нарушает обычное соглашение о добавлении слушателей. Слушатели добавляются в начало списка, а не в конец. Это даст мне вывод вроде:

The state in B is 0
Launch a specific event!
The state in A is 0
The state in B is 1
The state in A is 1
The state in B is 2
The state in A is 2

Значит, сначала звонят Б., но, по крайней мере, они в хорошем состоянии. Если я просто подтолкну слушателей к списку, я получу:

The state in A is 0
The state in A is 1
The state in A is 2
The state in B is 2
The state in B is 1
The state in B is 0
Launch a specific event!

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

Из двух, я думаю, что первый является лучшим (если не переписывать архитектуру / проблему). Это немного сложнее, но нигде не требует предварительных знаний, и события вызываются в правильном порядке.

Если кто-то не может предложить лучшую архитектуру для этой проблемы, я пойду с этим.

0 голосов
/ 14 сентября 2011

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

0 голосов
/ 11 июля 2011

Фактическая проблема заключается в том, что Java копирует и передает все аргументы по значению. С примитивами (например, int i) это приводит к тому, что каждый метод получает свою собственную копию значения. Обычно это защищает от того, что вы на самом деле пытаетесь сделать, так как в противном случае побочные эффекты метода могут удивить людей.
Быстрый и простой способ исправить это - вернуть измененное значение i, поэтому ваш вызов выглядит следующим образом: i = super.foo(i);. Это имеет два преимущества: 1) оно дает людям понять, что вы ожидаете, что значение i может измениться, и 2) вы не зависите от побочного эффекта, который может изменить значение.
В противном случае, вы могли бы изменить i, чтобы он был чем-то вроде обёртки объекта (я не решаюсь сказать Integer, потому что есть некоторые вещи оптимизации под прикрытием, которые могут испортить этот дизайн). Но если вы сделаете это, задокументируйте желаемое поведение в Wazoo, или разработчики, использующие этот метод, могут быть удивлены изменением значений (или будущие сопровождающие могут корректно не корректировать значения).

<ч /> РЕДАКТИРОВАТЬ: Ладно, исходя из его звучания, вы хотите отреагировать как на текущее состояние , так и на предыдущее состояние, , но вы никогда не сохраните предыдущее состояние ...
Понятно, что вам придется каким-то образом сохранять предыдущее состояние - локальная переменная внутри b._onStateChanged(), вероятно, является лучшим выбором. В противном случае ваша программа продолжит реагировать на текущее состояние (просто текущее состояние не соответствует ожиданиям).
Кроме того, вы можете немного изменить свою архитектуру - поскольку вы никогда не знаете, будет ли по-прежнему выполняться ваше поведение по умолчанию (внутри A). Посмотрите снова на рекомендацию @ Don, так как я подозреваю, что это будет больше в направлении, в котором должна развиваться ваша архитектура (вы всегда хотите общее, верно?). Другими словами, сначала не вызывайте определенное поведение - вызовите универсальный, сделайте так, чтобы он выполнил свои модификации, и пусть он вызовет определенное поведение, когда это будет сделано. При необходимости вы также можете рекурсивно вызывать метод setFoo(), позволяя ему вызывать setFooCallback() для каждого измененного состояния.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...