Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to write an interface to cover both terminal and 2D UI

Tags:

java

Java8: I want to create an interface as follows:

public interface Ui {
    abstract static void homeScreen();
    abstract static void exitSplash();
}

But I get errors:

error: illegal combination of modifiers: abstract and static
    abstract static void homeScreen();

And for static void abstract:

error: <identifier> expected
        static void abstract homeScreen();

I want to be able to create a class that has the above methods, where I can write my own method bodies, but have them static, because I don't need to create an object in order to show a home screen or an exit splash (surely I don't - or am I ignorantly trying to force a non OOP approach?).
I don't want to add parameters to the interface methods because imagine two implementations -

  1. A text-based UI, and
  2. A 2D UI.

What arguments would they have in common? One will be all Swing or equivalent, and the other will be nothing to do with Swing.

There are two questions here:

  1. I think it really boils down to I don't understand the basics. And someone will undoubtedly accuse me of 'not grasping the fundamentals of OOP programming'. But I HAVE read - quite a large chunk of books - but when I try to put things into practice, it requires just that - practice! Can someone please point me in the right direction with regards to allowed/disallowed combinations of abstract, static etc. for interface method descriptions.

  2. How can I make an interface method for a static class method with no arguments. Its not possible is it!? So how do I solve my little problem of wanting an interface for UI, that can best cover Terminal AND 2D UIs? Give-up and just instantiate the class> Why should I?

NOTE: Here is the (test) class I would like to build an interface to cover. I like it the way it is, with static methods:

import java.io.IOException;
import jline.console.ConsoleReader;

class TestGameScreen2 implements Ui {
    private static ConsoleReader con;
    public static void homeScreen() {
        try {
            con.setPrompt("AwesomePrompt> ");

            Screen.clear();
            System.out.println("Press any key to continue...");
            con.readCharacter();
            Screen.clear();

            System.out.println("Here is a prompt. Do something and press enter to continue...");
            String line = con.readLine();
            Screen.clear();

            System.out.println("You typed: ");
            System.out.println(line);
            System.out.println("Press any key to exit. ");
            con.readCharacter();
            Screen.clear();
        } catch (IOException e) {
            e.printStackTrace();

    }
    }
    public static void exitSplash() {
        System.out.println("Thank You. Goodbye.");
        System.out.println("");
    }
    public static void main (String argv[]) {
        try {
            con = new ConsoleReader();
            homeScreen();
            exitSplash();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
like image 342
jdurston Avatar asked Dec 28 '25 23:12

jdurston


1 Answers

The reason why you cannot define a method as both static and abstract:

abstract methods require implementation (via inheritance). static members belong to the class, removing intentions of inheriting. You cannot override a static method, only hide it.

You are most definitely forcing the "non-OOP" approach. There is no reason you should need static for this.

Short-lived objects are very common in object oriented code. Heap is structured specifically to handle objects with different lifetimes. A splash/welcome screen would be a great example of such, only existing for a short period of time.


As for your design, I see you're worried about applying proper OOP design principles. Here's a few flaws I was able to point out:

Single Responsibility: Shoving the code for starting and ending your splash into the UI type makes UI responsible for the splash. You should keep the responsibilities low by allowing a SplashScreen type to handle such. You could then have the UI request a SplashScreen:

abstract class UI {
    private SplashScreen splash;

    public UI(SplashScreen splash) {
        this.splash = splash;
    }

    public void start() {
        splash.start();
        init();
        splash.end();
    }

    public abstract void init();
}

The init works as a template method, allowing implementations to initialize themselves during the splash. By moving the code for the splashscreen into another object, we lower the responsibility of the UI. On top of this, you can easily switch splash screens without modifying the class by passing in a different SplashScreen implementation (abiding by the open/close principle).

Interface Segregation: Not all UIs have splash screens. This means some implementations may not make use of startSplash and exitSplash. Instead, you should segregate the interface. Create different abstractions: one which supports splash and one that doesn't:

abstract class UI {
    public abstract void start();
}

abstract class SplashableUI {
    private SplashScreen splash;

    public SpashableUI(SplashScreen splash) {
        this.splash = splash;
    }

    public final void start() {
        splash.start();
        init();
        splash.end();
    }

    public abstract void init();
}

Interfaces are nice, but they can only abstract behavior, not state. Design with scalability in mind. You may want to add some shared state to your UI type. That would force you to reconstruct all it's implementations in order to convert it to abstract class.

Personally, I use interfaces to bridge the gap between different type hierarchies via behavior. For example:

              Entity
            /           \
Humanoid    Animal
         |               /     \
   Human    Bird    Fish

Human and Fish branch off into 2 different type hierarchies. But they can both swim(). If we wanted a SwimmingPool class which accepts anything that could swim, we have 3 options:

  1. Declare all types that can swim in the SwimmingPool class (accept(Person), accept(Fish), ect...). But that's not scalable
  2. Create a SwimmableEntity type. But then we would need NonSwimmableEntity. On top of that, we would need SwimmableAnimal and NonSwimmableAnimal... You'd have a bunch of very similar interfaces, creating more type hierarchies than needed.
  3. Have Fish and Human implement a Swimmable interface, then allow SwimmingPool to accept objects of that type.
like image 176
Vince Avatar answered Dec 30 '25 12:12

Vince