3

I am working on a ML project that will involve malware and I want to represent the statistics files used for training the classifier via StatsFile objects. However, I am wondering whether I should use a IStatsFile and subclass as such:

IStatsFile
TrainingMWStatsFile
TrainingBWStatsFile
TestingMWStatsFile
TestingBWStatsFile

Now MW and BW and testing and training files have no fundamental difference except how they are used by the system. So basically all of these just inherit methods from the base class and contain no additional code.

Is this overkill? Would it be better just to have a StatsFile that contained this information in variables? i.e.:

statsfile.training = True
statsfile.MW = true

My instince is to say that the second way is better. But I do really like being able to do the following:

if type(statsfile) is not MWStatsFile:

versus

if statsfile.MW == false

Thoughts on best practice here?

2
  • 4
    General rule of thumb: avoid subclassing when you can. This is where the phrase "prefer composition over inheritance" comes from. Subclassing is expensive, and suffers from things like "brittle base classes," so use it only when it elegantly solves a significant design problem. Sep 10, 2015 at 17:41
  • From what you've said so far, it seems like a single StatsFile with a type enum would completely solve this problem; imo you don't appear to have the complexity/extensibility requirements to justify the strategy pattern described below. Am I missing something?
    – Ixrec
    Sep 11, 2015 at 7:16

1 Answer 1

3

Really, neither way is better.

I think the true problem does not originate in the IStatsFile class hierarchy. The code that contains the if statements testing the stats file objects for which type they are is truly to blame for this. You have a stats file, but several different things you can do with it. The Strategy Pattern can help out here.

From Wikipedia:

In computer programming, the strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime.

...

For instance, a class that performs validation on incoming data may use a strategy pattern to select a validation algorithm based on the type of data, the source of the data, user choice, or other discriminating factors. These factors are not known for each case until run-time, and may require radically different validation to be performed. The validation strategies, encapsulated separately from the validating object, may be used by other validating objects in different areas of the system (or even different systems) without code duplication.

Another, probably more common, use of the Strategy Pattern is formatting log messages. Consider log messages with this information:

  • Log level: INFO
  • Message: "The quick brown fox"
  • Date: "2015-09-10 01:05 PM EDT"

There are a number formats in which you could log this information to a file:

INFO 2015-09-10 01:05 PM EDT: The quick brown fox

[INFO, 2015-09-10 01:05 PM EDT] The quick brown fox

INFO (2015-09-10 01:05 PM EDT) The quick brown fox

And the list goes on. The Strategy Pattern can be used to provider your logger class with a format strategy, so changing the format of the log files doesn't require changing the logger class itself. Just provide a different Format Strategy object.

A quick code example is in store. Interfaces are a good starting point for the Strategy Pattern, because it abstracts away the implementation of the strategy -- which is the whole point of the Strategy Pattern:

public interface LogFormatStrategy {
    String formatLogMessage(String level, String message, LocalDateTime date);
}

(Note, I'm using Java for this example)

The LogFormatStrategy interface is dead simple. It just specifies a single method to do what it needs to do.

Now we can create as many log formatters as we desire:

// [INFO, YYYY-MM-DD HH:MM:SS] The quick brown fox
public class SquareBracesLogFormatStrategy implements LogFormatStrategy {
    public String formatLogMessage(String level, String message, LocalDateTime date) {
        return "[" + level.toUpperCase() + ", " + date + "] " + message;
    }
}

// INFO (YYYY-MM-DD HH:MM:SS) The quick brown fox
public class ParenthesisLogFormatStrategy implements LogFormatStrategy {
    public String formatLogMessage(String level, String message, LocalDateTime date) {
        return level.toUpperCase() + " (" + date + ") " + message;
    }
}

// INFO YYYY-MM-DD HH:MM:SS The quick brown fox
public class SimpleLogFormatStrategy implements LogFormatStrategy {
    public String formatLogMessage(String level, String message, LocalDateTime date) {
        return level.toUpperCase() + " " + date + " " + message;
    }
}

And a quick, mostly incomplete Logger class to use the format strategies:

public class Logger {
    private LogFormatStrategy formatter;

    public Logger(LogFormatStrategy formatter) {
        setFormatter(formatter);
    }

    public void info(message) {
        String logMessage formatter.formatLogMessage("INFO", message, LocalDateTime.now());

        // write logMessage to a file
    }

    public void setFormatter(LogFormatStrategy formatter) {
        this.formatter = formatter;
    }
}

Now we can log in different formats with the same logger object, or give two different logger objects different format strategies.

Logger logger = new Logger(new SimpleLogFormatStrategy());

// INFO YYYY-MM-DD HH:MM:SS The quick brown fox
logger.info("The quick brown fox");

logger.setFormatter(new SquareBracesLogFormatStrategy());

// [INFO, YYYY-MM-DD HH:MM:SS] The quick brown fox
logger.info("The quick brown fox");

This design pattern would alleviate, if not eliminate, the need for sub classes.

7
  • 2
    This is the implementation of "composition over inheritence." Your class is the composition of the stats file and the methods to do whatever it is you do after if statsfile.MW == false Sep 10, 2015 at 18:29
  • I don't really see the difference considering this also uses inheritance/subclassing just in a different way.
    – user123959
    Sep 10, 2015 at 18:56
  • Composition and the Strategy Pattern are kind of related. A class can be composed of one or more strategies that operate on that class. The key here is other code in the system peppered with if statements that then do something specific with the stats file. That is the significant difference here. Sep 10, 2015 at 18:56
  • @user123959: There is no sub-classing here. An interface is being used, that multiple unrelated classes implement. Sep 10, 2015 at 19:04
  • 1
    @user123959: I think we need more context for your problem. Sep 10, 2015 at 20:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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