0
\$\begingroup\$

I am new to Design Pattern implementation and would like to strongly solidify my knowledge to become as good of a programmer as I can. Is this a proper implementation of the Abstract Factory design? The idea here is that I can interchangeably utilize either a ConsoleLogger or a FileLogger for three different "levels" of logging (Execution, Output, and Error). I want the client to be able to log Execution and Output to console, but Error logs to a file if necessary.

Logger Interface:

public interface ILogger {
    public void log(String content);
}

Base Logging Factory:

import java.util.HashMap;

public abstract class LoggingFactory {
    static {
        factoryMap = new HashMap<String, LoggingFactory>();
    }

    private static HashMap<String, LoggingFactory> factoryMap;

    public abstract ILogger getLogger();

    public static void register(String type, LoggingFactory factory) {
        factoryMap.put(type, factory);
    }

    public static LoggingFactory getFactory(String type) {
        return factoryMap.get(type);
    }
}

ConsoleOutputLoggingFactory:

public class ConsoleOutputLoggingFactory extends LoggingFactory {
    static {
        LoggingFactory.register("ConsoleOutputLoggingFactory", new ConsoleOutputLoggingFactory());
    }

    @Override
    public ILogger getLogger() {
        return new ConsoleOutputLogger();
    }
}

ConsoleOutputLogger:

public class ConsoleOutputLoggerimplements ILogger {
    @Override
    public void log(String content) {
        System.out.println("ConsoleOutputLogger " + content);
    }
}

FileOutputLoggingFactory:

public class FileOutputLoggingFactory extends LoggingFactory {
    static {
        LoggingFactory.register("FileOutputLoggingFactory", new FileOutputLoggingFactory());
    }

    @Override
    public ILogger getLogger() {
        return new FileOutputLogger();
    }
}

FileOutputLogger:

public class FileOutputLogger implements ILogger {
    @Override
    public void log(String content) {
        //write output to file
    }
}

ConsoleErrorLoggingFactory:

public class ConsoleErrorLoggingFactory extends LoggingFactory {
    static {
        LoggingFactory.register("ConsoleErrorLoggingFactory", new ConsoleErrorLoggingFactory());
    }

    @Override
    public ILogger getLogger() {
        return new ConsoleErrorLogger();
    }
}

ConsoleErrorLogger:

public class ConsoleErrorLogger implements ILogger {
    @Override
    public void log(String content) {
        System.out.println("ConsoleErrorLogger" + content);
    }
}

FileErrorLoggingFactory:

public class FileErrorLoggingFactory extends LoggingFactory {
    static {
        LoggingFactory.register("FileErrorLoggingFactory", new FileErrorLoggingFactory());
    }

    @Override
    public ILogger getLogger() {
        return new FileErrorLogger();
    }
}

FileErrorLogger:

public class FileErrorLogger implements ILogger {
    @Override
    public void log(String content) {
        //write error to file
    }
}

Factory Initializer:

public class LoggingInitializer {
    public LoggingInitializer() {
        LoggingFactory.register("ConsoleOutputLogger", new ConsoleOutputLoggingFactory());
        LoggingFactory.register("FileOutputLogger", new FileOutputLoggingFactory());
        LoggingFactory.register("ConsoleErrorLogger", new ConsoleErrorLoggingFactory());
        LoggingFactory.register("FileErrorLogger", new FileErrorLoggingFactory());
    }
}

Main:

public class AbstractFactoryExample {
    public static void main(String[] args) {
        new LoggingInitializer();

        ILogger outputLogger = LoggingFactory.getFactory("OutputLogger").getLogger();
        if (outputLogger != null)
            outputLogger.log("Hello World");
    }
}
\$\endgroup\$
1
\$\begingroup\$
static {
    factoryMap = new HashMap<String, LoggingFactory>();
}

private static HashMap<String, LoggingFactory> factoryMap;

can be simplified to the following without any changes in functionality:

private static final Map<String, LoggingFactory> factoryMap = new HashMap<>();

public class LoggingInitializer {
    public LoggingInitializer() {
        LoggingFactory.register("ConsoleOutputLogger", new ConsoleOutputLoggingFactory());
        LoggingFactory.register("FileOutputLogger", new FileOutputLoggingFactory());
        LoggingFactory.register("ConsoleErrorLogger", new ConsoleErrorLoggingFactory());
        LoggingFactory.register("FileErrorLogger", new FileErrorLoggingFactory());
    }
}

The initialization of a class only happens in it's initializer blocks. Nowhere else.
What you're doing here is wiring up dependencies. Use a DI framework for that ...

If you were going to set up the factories this way in the first place, you could've just not have them register themselves. It's weird that they register themselves anyways. That results in the following changes:

  1. LoggerFactory implementations don't need to be derived from LoggingFactory to have access to the factoryMap, only to conform to the getLogger interface.
  2. Actually the getLogger interface is what you'd have in a functional interface in modern Java.

This results in simplifications to your API. Instead of explicitly constructing factories and using getFactory, you're off much better hiding the implementation detail of Factories and instead using a Facade, like so:

public static final class LoggingFactory {
    private static final Map<String, Supplier<ILogger>> factoryMap = new HashMap<>();
    static {
        ILogger consoleOutLoggerSingleton = new ConsoleOutputLogger();
        factoryMap.put("ConsoleOutputLogger", () -> consoleOutputLoggerSingleton);

        ILogger fileOutputLoggerSingleton = new FileOutputLogger();
        factoryMap.put("FileOutputLogger", () -> fileOutputLoggerSingleton);

        ILogger consoleErrorLoggerSingleton = new ConsoleErrorLogger();
        factoryMap.put("ConsoleErrorLogger", () -> consoleErrorLoggerSingleton);

        ILogger fileErrorLoggerSingleton = new FileErrorLogger();
        factoryMap.put("FileErrorLogger", () -> fileErrorLoggerSingleton);
    }

    public static ILogger getLoggerFor(String name) {
        return factoryMap.getOrDefault(name, () -> new NoOpLogger()).get();
    }
}

This cuts away the "man in the middle" of your factory, not to mention the initialization outside the class. It also is more in line with usual Java logging APIs, where you directly obtain the logger.

In closing you should be aware that reinventing logging is usually a "Bad Idea™". If this hadn't been prefixed, I'd have washed your head for it :)

Finally: Just because there is a factory, the client of you API doesn't need to know- Generally the less indirection your API has, the easier it will be to use. Strive to minimize ways through your API.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.