Home > database >  Modify functionality of Parent class without rewrite in java
Modify functionality of Parent class without rewrite in java

Time:11-29

Let's say I have an abstract class, called Logger:

public abstract class AbstractLogger {
public enum Levels {
    DEBUG, INFO, WARNING, ERROR
}

public void debug(String message) {
    Levels level = Levels.DEBUG;
    log(level, message);
}

public void info(String message) {
    Levels level = Levels.INFO;
    log(level, message);
}

public void warning(String message) {
    Levels level = Levels.WARNING;
    log(level, message);    }

public void error(String message) {
    Levels level = Levels.ERROR;
    log(level, message);    }

public void log(Levels level, String message) {}

}

And I also have classes that inherit this class, such as FileAppenderLogger:

public class FileAppenderLogger extends AbstractLogger {
private final Path logPath;

public FileAppender(Path logPath) {
    this.logPath = logPath;
    createLogFile();
}

private void createLogFile() {
    try {
        File logFile = new File(logPath.toString());
        if (logFile.createNewFile()) {
            System.out.println("File created: "   logFile.getName());
        } else {
            System.out.println("File already exists.");
        }
    } catch (IOException e) {
        System.out.println("An error occurred.");
        e.printStackTrace();
    }
}

@Override
public void log(Levels level, String message) {
    try {
        FileWriter myWriter = new FileWriter(this.logPath.toString());
        myWriter.write(message "\n");
        myWriter.close();
        System.out.println("Successfully wrote to the file.");
    } catch (IOException e) {
        System.out.println("An error occurred.");
        e.printStackTrace();
    }
}

@Override
public void debug(String message) {
    super.info(message);
}

@Override
public void info(String message) {
    super.info(message);
}

@Override
public void warning(String message) {
    super.warning(message);
}

@Override
public void error(String message) {
    super.error(message);
}

}

Now, let's say I need to extend Logger to support new Log level, such as "FATAL", and also extend its children, such as FileAppenderLogger to support it, without modify any of those classes, only extend them. what could be the best practice for that (if I still want to preserve non generic methods such as ".info(String s)" or ".debug(String s))? What design pattern may I use here? I'm open for changes regard this problem. Thank you!

CodePudding user response:

Simply add it to AbstractLogger:

public abstract class AbstractLogger {
  public enum Levels {
      DEBUG, INFO, WARNING, ERROR, /* added */ FATAL,
  }

  public void fatal(String message) {
    log(Levels.FATAL, message);
  }
}

Given that the types that extend AbstractLogger all already implement the log method, then 'things will just work' - possibly some of the implementations cannot deal with the fact that a new log level has now appeared. Assuming they were appropriately programmed, they'll throw. Your FileAppenderLogger class, for example, would just continue to work without requiring any change or even recompilation.

The key design pattern to make this work is that all those non-generic methods such as .error(x) are light wrappers that all send the data to a single method that does the real work - log. But, you already do that.

NB: Reinventing the wheel is a bad idea. Logging frameworks already exist, use an existing one instead.

NB2: Idiomatic java dictates you call your enum types the singular - it should be Level, not Levels. The type name describes, well, a type name. It's called String, not Strings, because an instance of java.lang.String represents one string. The class itself represents all strings, but that doesn't mean it should be called Strings. Similarly, an instance of the Levels enum represents a single level. Hence, it should be named Level, not Levels.

CodePudding user response:

Instead of using enum for level, you can make class LogLevel and make classes that extend it, for example LogLevelError, LogLevelFatal, then in log method: this.logLevel.log(message);. Of course, it look strange, but this is the way I see to add new log levels. Also, as said by @rzwitserloot :"NB: Reinventing the wheel is a bad idea. Logging frameworks already exist, use an existing one instead". They are much faster, optimized, and 'time-tested'.

CodePudding user response:

You can't add more values to your enum, that's not possible in java. I would suggest to either use a String for levels, or declare your own Level class, so you can add more levels.

public class Level {

    private final String levelName;

    //getter, etc.
}

To extend the functionality of your AbstractLogger, without modifying it, you can wrap it in another class and declare the additional methods, fatal() in this case.

public class ExtendedLogger extends AbstractLogger {

    private final AbstractLogger abstractLogger;

    public ExtendedLogger(AbstractLogger abstractLogger) {
        this.abstractLogger = abstractLogger;
    }

    @Override
    public void debug(String message) {
        abstractLogger.debug(message);
    }

    //info, warning and rest of methods

    @Override
    public void log(Levels level, String message) {
        abstractLogger.log(level, message);
    }

    public void fatal(String message) {
        //implement
    }
}

CodePudding user response:

First: logger libraries are numerous, and the first reform was the introduction of java.util.Logger to unify things a bit. Still not the dead of the other logging libraries.

Then came - especially for libraries - the underestimated System.Logger: a Logger façade that can be discover logging implementations. This allows publishing a library, use Logging, but leave the actual logging library choice to the library user.

So in that context meddling in class hierarchies and enum constants is counter-productive to say the least.

What you can do is a specific configuration, say for some packages, implement a specific file handler (FileHandler, Handler), and reserve ERROR for your own FATALISH when using *Exception classes or such.

Though seemingly simple, using FileWriter in the Logger child was not intended to be done as such. You should leave it to configuring the usage to your own Handler class.

Unfortunately the solution does not exist. You'll better write a prototype to test your specific configuration.

  • Related