superclasse vuota per la raccolta di classi derivate

2

In sostanza, ciò che vorrei ottenere è un modo per scorrere attraverso un elenco e chiamare metodi specifici per l'interfaccia degli oggetti nell'attrezzatura di raccolta.

Nel mio progetto Java, si otterrebbe qualcosa di simile a questo:

//GameComponent would be my empty abstract class  
HashSet<GameComponent> components = new HashSet<GameComponent>(); 
components.add(/* instance of a class that extends GameComponent and implements Drawable || Updatable */);  

for (Drawable d : components)
    d.draw();

for (Updatable u : components)
    u.update();

I miei amici mi hanno suggerito di creare solo due elenchi separati, ma ciò significa che alcuni oggetti saranno presenti in entrambi gli elenchi.
Penso anche che abbia senso (astrazione-saggio) avere una lista di GameComponents e scorrere attraverso quella lista.
Ma anche a me non piace avere una classe vuota da ereditare, solo per la categorizzazione.

Quindi la domanda è: il mio approccio ha senso? Se è così, è elegante? In caso contrario, quale modello potrei seguire?

    
posta Carlo Vespa 10.04.2014 - 13:26
fonte

3 risposte

3

Un paio di pensieri:

  1. È meglio se i componenti del gioco non sanno come disegnare. Una classe dovrebbe avere solo una ragione per cambiare; qui i tuoi componenti potrebbero cambiare se hai bisogno di cambiare ciò che fanno in-game, e potrebbero cambiare se hai bisogno di cambiare il modo in cui vengono disegnati.

  2. Per questo scenario la soluzione più semplice è usare instanceof . Non è la soluzione più generale, ma è la cosa più semplice che risolverà questo particolare problema.

    for (GameComponent component : gameComponents) {
        if (component instanceof Drawable) {
            ((Drawable) component).draw();
        }
        if (component instanceof Updatable) {
            ((Updatable) component).update();
        }
    }
    
  3. Se hai un set finito di tipi di GameComponent, quello che vuoi è un tipo di somma , che è il mainstream I linguaggi OOP non forniscono supporto nativo, ma puoi emulare attraverso il Pattern visitatori . (C'è un alternativa modo di implementarle ma non sarà elegante in Java, funziona meglio con gli argomenti con nome e le espressioni lambda.) Questa è una soluzione più generale per prendere decisioni basate sul particolare tipo di GameComponent perché ti dà pieno accesso al tipo di componente.

Sembrerebbe qualcosa del genere:

    public interface ComponentVisitor {
         void visit(Foo foo);
         void visit(Bar bar);
         void visit(Baz baz);
    }

    public abstract class GameComponent {
        // prevent subclassing outside this scope
        private GameComponent() {}

        public abstract void accept(ComponentVisitor visitor);

        public static final class Foo extends GameComponent implements Drawable, Updatable {
            // implement ...

            public void accept(Componentvisitor visitor) {
                visitor.visit(this);
            }
        }

        public static final class Bar extends GameComponent implements Updatable {
            // implement ...

            public void accept(Componentvisitor visitor) {
                visitor.visit(this);
            }
        }

        public static final class Baz extends GameComponent {
            // implement ...

            public void accept(Componentvisitor visitor) {
                visitor.visit(this);
            }
        }
    }

Ora puoi prendere decisioni specifiche per i componenti come questa:

    for (GameComponent component : gameComponents) {
        component.accept(new ComponentVisitor() {
            public void visit(Foo foo) {
                // do something Foo-specific here
            }

            public void visit(Bar bar) {
                // do something Bar-specific here
            }

            public void visit(Baz baz) {
                // do something Baz-specific here
            }
        });
    }

Funziona perché ogni componente ha bisogno di implementare accept , e per visit(this) per compilare il Visitor deve avere un metodo che accetti quel particolare tipo di componente. È ancora possibile implementare un componente in modo che accept non chiami visit(this) , quindi ci salvaguardiamo da ciò utilizzando un costruttore privato. Ciò impedisce la sottoclasse esterna mentre consente la sottoclassificazione delle classi interne (perché le classi interne hanno accesso ai membri privati della loro classe che li racchiude.) Il risultato finale è un insieme finito di componenti e un modo esaustivo per discriminare tra loro (non si può dimenticare di gestire uno di questi perché l'implementazione di Visitor non sarebbe stata compilata.)

    
risposta data 10.04.2014 - 14:07
fonte
0

Alcune cose da considerare:

  • Perché GameComponent è vuoto? Se ci sono metodi / proprietà condivisi tra tutti i bambini di GameComponent, non dovrebbe essere una classe base astratta vuota.

  • GameComponent deve essere una classe? Se non stai definendo alcun comportamento o valori predefiniti, puoi anche renderlo un'interfaccia.

Un'interfaccia vuota non è più netta / più elegante di per sé, ma usando le interfacce per "taggare" un oggetto come di un certo tipo, manterremo la sua capacità di estendere un'altra classe.

    
risposta data 10.04.2014 - 13:40
fonte
0

Non vedo problemi con questo.

Ad esempio, quando effettuo l'accesso, verrà visualizzato un risultato di accesso:

public abstract class LoginResult { ... }

Quando accedo, potrei avere tre scenari:

  • Accesso non riuscito in quanto la password non corrisponde
  • Accesso riuscito
  • Accesso riuscito, ma l'account è bannato

Per fare questo, vorrei quindi introdurre tre classi che ereditano dalla base:

public class SuccessfulLoginResult : LoginResult 
{
   // May contain a string with a welcome message
}

Uno per quando l'accesso non riesce:

public class FailedLoginResult : LoginResult
{
    // String containing information as to why the login has failed and integer property indicating how many login attempts are left
}

Uno per quando l'account è bannato:

public class AccountSuspendedLoginResult : LoginResult
{
   // String containing information as to why account is suspended and potentially a date time field indicating when account will be activated again
}

Vorrei quindi utilizzarlo nel modo seguente (C #):

var loginResult = _accounts.Login ("Username", "Password");

if (loginResult is SuccessfuLoginResult)
{
   // Do something
}
else if (loginResult is FailedLoginResult)
{
   // Do something else
}

La classe base può rimanere vuota e non vedo alcun problema.

    
risposta data 10.04.2014 - 17:13
fonte

Leggi altre domande sui tag