1

Hello to everyone and thank you for any suggestion.

I have a family of subclasses of Track (Track is abstract).
Every concrete Track has a different number and types of fields (attributes) that extend the basic abstract track-fields.

Then I have a family of file-types that implement some methods (saveRow() etc..).
Every file is presumed to have different type of row-formatting (imagine csv tabs, headers etc..)

ex:

  • SimpleTrack: double lat, double lon, Calendar dateTime.
  • DetectionTrack: (as SimpleTrack) + boolean detection.
  • ..
  • CsvFile
  • TxtFile
  • ...

When I create a new (X)Track and a (Y) file, they are independent by nature, but.. the row is a cartesian product of track & row types.

EDIT
(to be more clear): How can I have many concrete-tracks in one hand and many FileTypes in the other hand, and create well-formatted-rows (differents for every file) by tracks which have different data (columns, headers..)?
es:

  • XtrackRow(double a, double b, Calendar date) -> to -> CVSfile (tab delimited with headers)
  • XtrackRow(double a, double b, Calendar date) -> to -> TXTfile (formatted columns and title)
  • YtrackRow(double a, string, b, int c, double e) -> to CSV ..
  • YtrackRow .... -> to -> docx file (with another kind of table or tabulation)
    ..

I see two kinds of solutions:

  1. Tracks send (right)-formatted row to file: every track has to know which kind of format apply (to rows) to give them to save to any specific type of file.
  2. Tracks send raw-data to any kind of file which is responsible to format them: in this case, the file-class must know wich kind of data has to format (every track has diffrent contents, columns, headers..). Moreover, every track-class has to send different number and types of parameter..

The second solution seems to be more fitted to the Single-Responsibility principle.. but I have no Idea how to implement it.

I tried to use Bridge Pattern to solve this problem (using first solution):

abstract class Track{
  ...
  FileInterface file;

  Track(FileInterface fileType){
    this.file = fileType;
  }

  abstract String formatConcreteTrackRow();
  
  void sendRow(){
    String rowToSave = formatConcreteTrackRow();
    file.saveRow(rowToSave);
  }
}

By this way the problem is not already solved, because every concrete-track has to implement a set of methods which returs right formatted rowString: one for every file-type. If I use a Strategy Pattern:

class SimpleTrack extends Track{
  ...
  RowFormatStrategy rowStrategy;
  
  @override
  String formatConcreteTrackRow(){
    return this.rowStrategy.getRowString("args")
}

but in this case.. every concrete-track require a different StrategyInterface, because every concrete-track has different number and types of arguments to elaborate..
If I do not use Strategy Pattern and I define a set of methods (formatCsvRow(args),formatTxtRow(args)..) I need to include a switch(fileType) loop to choose which method to use.. breaking SOLID principles.. :(

Moreover..
how to impose, for every new concrete-track to have right row-format methods for every existent file-template-row?
and.. How to impose, at the same time, for every new file-class to impose new templates and relative methods in every existent concrete-track?

To be honest, it's also quite reductive impose formatConcreteTrackRow to be a String, but it's over and over the main problem.

I'm not interested to maintain this kind of class structure, this is only the best solution I found trying to follow SOLID principles. If you can show me a better solution, my intent is to study and understand SOLID procedures to solve these kind of purposes.

(I looked around for similar questions, but I'm not even able to define the specific problem itself..)
Thank you very much.

11
  • 1
    Part of your problem is that you are applying design patterns without really knowing the kind of problem you are trying to solve. Until you do that, there is a chance that you are using a pattern for the wrong reason; thus, not addressing the problem.
    – hfontanez
    Apr 10, 2021 at 22:04
  • I think I explained enough the problem, I know exactly what I 'm trying to do, but your your contribute is very usefull.
    – Marco
    Apr 10, 2021 at 22:23
  • 1
    you explained your situation, not the type problem you are trying to solve. For example, you mentioned that you tried to use Strategy Pattern and Bridge Patterns to solve "your problem". But your problem appears to be a creational issue and none of these patterns are creational in nature. So, what I am trying to say is that, maybe the problem is clear in your head and what you wrote makes sense to you but your audience (me) is confused because problem statement and what you said you have tried do not appear to match.
    – hfontanez
    Apr 10, 2021 at 23:19
  • Also, SOLID principles are guidelines to write cleaner, robust code. They are not guidelines to solve problems. Design Patterns are ways to apply general solution to specific TYPES of problems.
    – hfontanez
    Apr 10, 2021 at 23:23
  • 1
    @BenCottrell: to be fair, some of the classic GoF patterns are providing means for some of the SOLID principles. For example, the OCP: "strategy" is probabably the most obvious one, but "Abstract factory", "Prototype", "Template Method" or "Observer" are supporting the OCP, too. Others aim for the SRP, like "Builder", Bridge" "Decorator" or "Mediator". So I think the term "orthogonal" does not tell the full story.
    – Doc Brown
    Apr 11, 2021 at 15:52

4 Answers 4

3

I'd suggest you think first not about any patterns, but about what problem do you want to solve. Think if you really need that every file type has many structure types. Think if it will be easy to parse such files, with big number of structures: many structure types of TXT files, many structure types of CSV file, etc. If yes, then such complexity is the price for this.

Check if you really need to support all the formats you currently plan. Exactly because of the complexity that you described TXT and CSV are not a good approach. Formats with flexible structure may fit much better. Consider JSON, YAML, HCL.

If you still want to use TXT or CSV, then following approach can help. In the file in each row write all attributes than are possible in all subclasses of Track. The serializer that writes an instance of particular class to the file should go through the list of all possible attributes and write them. In Java you can use reflection, it works very quick. In other languages you can create a map of atribute names and attribute values. The idea remains the same. The serializer requests an instance if it has attribute lat. If it has, it returns the value and the serializer writes it to the file. If value is not defined for the type of this instance, then serializer writes a value that means undefined. Then serializer requests the value of attribute lon and writes it to the file. Then it requests the value of attribute detection. The instance of SimpleTrack will respond that such attribute is not defined for this type, and serilizer will write a value meaning undefined. The instance of the class DetectionTrack will respons that the atribute is defined and will return the its value.

The what you really need is to write a serializer for everyy file type: One serializer for TXT (it will be able to serialize instances of any types), one serializer for CSV (it will be able to serialize instances of any types), etc.

6
  • Tank you @mentallurg, I would prefer to find a different way to solve this kind of structure without reflection, but I'll go deeper in your solution to see how to implement it and understand better.
    – Marco
    Apr 11, 2021 at 10:20
  • @Marco: It doesn't have to be reflection. You can use a model that knows all possible attributes. It can be a map of attribute name and values. In every Track-related class you implement a method that converts its attributes to a map. And serializer uses this map to write a string. Serializer does not need to know instance of what class has created this map. Just map.get("attributeA"), then write it. Then map.get("attributeB"), then write it, etc.
    – mentallurg
    Apr 11, 2021 at 13:32
  • Why don't you want to use reflection?
    – mmathis
    Apr 11, 2021 at 13:33
  • @mmathis: May be the author has read books or articles that referred reflection in Java in 2003 - 2005, when it was relatively slow. But starting from Java 5 reflection works very fast.
    – mentallurg
    Apr 11, 2021 at 13:37
  • I'm trying to study java structures, if I use such instruments I would evade my goal. Moreover, I've not studied reflection, and I prefer to avoid it until I'll be ready to next step. In any case, tank you.
    – Marco
    Apr 13, 2021 at 13:59
2

Design patterns are good for refining a design and talking about it with others, but they are not really suitable as building blocks (trying to fit a pattern to your problem usually doesn't work).

When looking at your problem, I see two natural angles:

  • From the perspective of a concrete Track, all those different file formats look completely different from each other without any common ground on how to construct a row.
  • From the perspective of a file format, those different Track types do have a common characteristic: The are all record types (they provide a set of fields with values). Even when the actual fields being provided are different, this commonality can be exploited in defining an interface that doesn't spread knowledge to the wrong places.

This interface could be something like

interface Track {
  /** Provide the set of fields supported by the concrete Track */
  public abstract List<String> GetFieldNames();

  /**
   * Write the value of the field identified by @arg name to @arg dest.
   * If the field doesn't extist, write the value of @arg default instead.
   */ 
  public abstract void WriteFieldValue(String name, Writer dest, Object default);
}

This way, the Track classes don't have to know about the various file formats, while the file format classes can get the information they need without having knowledge of each specific Track class.

The design even takes into account that a file format like CSV might have to merge the field lists from multiple different Track classes, with the result that a SimpleTrack might be asked to provide a value for the "detection" field that it has no knowledge of.

1
  • Tank you @bart-van-ingen-schenau, tank you very much! I was looking about something like. I'm studying how to implement such a feature with best fit to my structure. But tank you, you understood what I mean.
    – Marco
    Apr 13, 2021 at 13:54
0

You don't give a lot of details what a Track is or what the files are and what exactly is the problem you are solving. But the most basic answer to what you describe is very simple: all you need is an generic interface that returns a list of rows of elements.

In the simplest case you have Track define an abstract method abstract List<List<String>> getRows() and if you feel like it a abstract List<String> getAttributeNames() if you want names for each attribute displayed in a header. Alternatively you can return cells as Object and have toString defined in a meaningful.

Then your File implementation can iterate through the lists and do whatever they seem fit.

class CSVFile {
   /* ... */
   public writeFile(Track track, FileWriter fileWriter) {
       fileWriter.write(String.join(";", track.getAttributeNames())); 
       for (List<String> track_row : track.getRow()) {
           fileWriter.write(String.join(";", track_row));
       } 
   }
}
1
  • tank you @Helena, I'll try to implement also your solution. Sorry for bad problem explanation.
    – Marco
    Apr 13, 2021 at 13:55
-1

"How can I have many concrete-tracks in one hand and many FileTypes in the other hand, and create well-formatted-rows (differents for every file)"

You have to make a specific FileWriter per TrackType/FileType combination

ie

SimpleTrack_CSV_Writer
SimpleTrack_XML_Writer
DetectionTrack_CSV_Writer
DetectionTrack_XML_Writer

etc etc

You are forced to do this because the structure of the file format is more complicated than a simple flat output of each field on the Track object.

eg for the XML and DetectionTrack you could have:

DetectionTrack
{
   int x;
   int y;
   bool detected;
}

<row><x/><y/><det/></row>

OR

<row><position><x/><y/></position><det/></row>

DetectionTrack shouldn't know about the position node, its not part of it's responsibility. So it has no way to pass that information to a generic XML file writer.

1
  • Tank you @Ewan, but my intent is exatly to avoid this scenario.
    – Marco
    Apr 13, 2021 at 13:57

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.