1

I am implementing a log management system and want the types of logs to be extendible. We get a base object parsed from JSON (from Filebeat) such as:

class LogLine {
   String message
   Date timestamp
   String type
   String source
}

Given this LogLine object, I want to be able to create different objects, which will also extend this LogLine.

class SomeLog extends LogLine {
    int myfield
    String username
}

class SomeOtherLog extends LogLine {
   Date startTime
   Date endTime
   String username
   String transactionID
}

So, in my current non-ideal implementation:

void parse(String s){
   LogLine logLine = .....parseFromString(s)
   if ( logline.type.equals('def') ){
      SomeLog someLog = new SomeLog.Builder.build(logLine)
   } else if ( logline.message.containts('abc') ){
      SomeOtherLog someotherLog = new SomeOtherLog.Builder.build(logline)
   }
}

However, as you can imagine the builders in subclasses copies the superclass LogLine object, is there anyway I can do that without copying the values as they are already subclasses? Is there a design pattern to achieve this? I would not like to rely on reflection like BeanUtils.copyProperperties

Mustafa
  • 10,013
  • 10
  • 70
  • 116

2 Answers2

1

When you create a new object based on another it's a good idea to make a copy of all field. It's a best practice called defensive copying.

Since you parse a string, a defensive copy doesn't needed. Also I suppose you'll want to parse some specific fields from input string like myfield for SomeLog and startDate for SomeOtherLog. You could re-factor object creation like

LogLine result = null;
if (s.contains('type=def') { 
  result = SomeLog.parse(s);
} else if (trickyRegexp.mathces(s)) {
   result = SomeOtherLog.parse(s);
} else {
   result = LogLine.parse(s);
}

If you have many subclasses of LogLine then probably you'll want to move creation logic to a LogFactory which manages all the stuff regarding parsing string to specific object.

ADS
  • 708
  • 3
  • 14
1

Introduce a factory interface for creating LogLine objects.

public interface LogLineFactory {
    public LogLine createLog(LogLine logLine);
}

and use a Map for the lookup.

private Map<String, LogLineFactory > logLineFactories = new HashMap<>();

{
    logLineFactories .put("def", new SomeLogFactory());
    logLineFactories .put("abc", new SomeOtherLogFactory());
}

You can then ommit the if else branches using the map looup.

LogLine logLine = parseFromString(s);
LogFactory logFactory = logLineFactories.get(logLine.type);

if(logFactory != null) {
    LogLine wrappedLogLine = logFactory.createLog(logLine);
}

Maybe you will need more information to create the LogLines and you have to change the interface.

public interface LogLineFactory {
    public LogLine createLog(LogLine logLine, String s);
}

PS: with Java 8 you might want to use method references.

logLineFactories.put("def", SomeLog::new);
logLineFactories.put("abc", SomeOtherLog::new);    
René Link
  • 48,224
  • 13
  • 108
  • 140
  • The idea of using `Map` instead of `if-then-else if` pattern is great. But 'abc' substring is getting from a different field so you have to improve solution. – ADS Feb 15 '18 at 07:32