'Avoid code repetition in two different classes toString

Modeling a solution just to display(in console) the FreeCell card game, I have two classes Home and FreeCells, both way almost similar toString implementation

output something like this

+----+----+----+----+
| 2♣ | E  | E  | E  |
+----+----+----+----+

class Home

import java.util.HashMap;
import java.util.Stack;

public class Home {
    private final int MAX_CELLS = 4;
    private Stack<Card>[] homeCells;

    public Home() {
        this.homeCells = new Stack[4];
    }


    public boolean addACard(Card card) {
        //...some code
    }

    @Override
    public String toString() {
        StringBuilder box = new StringBuilder();
        box.append(buildHorizontalBorder());
        box.append("\n");
        box.append(buildContent());
        box.append("\n");
        box.append(buildHorizontalBorder());
        return box.toString();
    }

    private String buildContent() {
        StringBuilder contentBox = new StringBuilder();
        contentBox.append("|");
        for (int index = 0; index < MAX_CELLS; index++) {
            contentBox.append(" ").append(this.homeCells[index] == null ? "E " : this.homeCells[index].peek().toString()).
                    append(" ").append("|");
        }
        return contentBox.toString();
    }

    private String buildHorizontalBorder() {
        StringBuilder border = new StringBuilder();
        border.append("+");
        for (int i = 0; i < MAX_CELLS; i++) {
            border.append("----").append("+");
        }
        return border.toString();
    }
}

class FreeCell

import java.util.ArrayList;
import java.util.List;

public class FreeCells {
    private final int MAX_CELLS = 4;
    private List<Card> cells;

    public FreeCells() {
        this.cells = new ArrayList<>();
    }

    public boolean addCard(Card card) {
        //...some code
    }

    @Override
    public String toString() {
        StringBuilder box = new StringBuilder();
        box.append(buildHorizontalBorder());
        box.append("\n");
        box.append(buildContent());
        box.append("\n");
        box.append(buildHorizontalBorder());
        return box.toString();
    }

    private String buildContent() {
        StringBuilder contentBox = new StringBuilder();
        contentBox.append("|");
        for (int index = 0; index < MAX_CELLS; index++) {
            contentBox.append(" ").append(index < this.cells.size() ? this.cells.get(index).toString() : "E ").append(" ").append("|");
        }
        return contentBox.toString();
    }

    private String buildHorizontalBorder() {
        StringBuilder border = new StringBuilder();
        border.append("+");
        for (int i = 0; i < MAX_CELLS; i++) {
            border.append("----").append("+");
        }
        return border.toString();
    }
}

I am thinking of introdcuing a new class and abstract out the display logic to here

class Layout

public class Layout {
    private final int MAX_CELLS = 4;


    public String build(List<Card> cards) {
        StringBuilder box = new StringBuilder();
        box.append(buildHorizontalBorder());
        box.append("\n");
        box.append(buildContent(cards));
        box.append("\n");
        box.append(buildHorizontalBorder());
        return box.toString();
    }
    

    private String buildContent(List<Card> cards) {
        StringBuilder contentBox = new StringBuilder();
        contentBox.append("|");
        for (int index = 0; index < MAX_CELLS; index++) {
            contentBox.append(" ").append(index < cards.size() ? cards.get(index).toString() : "E ").append(" ").append("|");
        }
        return contentBox.toString();
    }

    private String buildHorizontalBorder() {
        StringBuilder border = new StringBuilder();
        border.append("+");
        for (int i = 0; i < MAX_CELLS; i++) {
            border.append("----").append("+");
        }
        return border.toString();
    }
}

Then change the method of toString of class Home to

    @Override
    public String toString() {
        return new Layout().build(Arrays.stream(this.homeCells).filter(Objects::nonNull).map(Stack::peek).collect(Collectors.toList()));
    }

Then change the method of toString of class FreeCell to

@Override
public String toString() {
    return new Layout().build(this.cells)();
}

Is this correct OO way, or i can do this better some other way?



Solution 1:[1]

In my view, we can separate logic of game from cards:

  • FreeCellGame class will be responsible for logic of game.
  • Cell, FreeCell and HomeCell will be responsible for cards.

We can extract the same logic of FreeCell and HomeCell in base class Cell. I mean methods toString() and buildHorizontalBorder(). I do not know Java, so I will show in C#. However, I will make comments how it can be converted to Java:

public abstract class Cell
{
    private int MAX_CELLS = 4;

    public abstract bool AddCart();

    // When you add some card, you need to remove
    // card from array where you took this card
    public abstract bool RemoveCart(Card card);

    public string toString() {
        StringBuilder box = new StringBuilder();
        box.Append(buildHorizontalBorder());
        box.Append("\n");
        box.Append(buildContent());
        box.Append("\n");
        box.Append(buildHorizontalBorder());
        return box.ToString();
    }

    internal abstract string buildContent();

    private string buildHorizontalBorder() {
        StringBuilder border = new StringBuilder();
        border.Append("+");
        for (int i = 0; i < MAX_CELLS; i++)
        {
            border.Append("----").Append("+");
        }
        return border.ToString();
    }
}

and its concrete implementations:

public class HomeCell : Cell // ":" extends in Java
{
    // other code is omitted for the brevity 
    
    public override bool AddCart()
    {  }

    public override bool RemoveCart(Card card)
    {  }

    internal override string buildContent()
    {  }
}

public class FreeCell : Cell // ":" extends in Java
{
    // other code is omitted for the brevity 
    
    public override bool AddCart()
    {  }

    public override bool RemoveCart(Card card)
    {  }

    internal override string buildContent()
    {   }
}

and game class is:

public class FreeCellGame
{
    private HomeCell homeCell = new HomeCell();
    private FreeCell freeCell = new FreeCell();

    private void AddCard()
    { 
        // your logic of game here
        // here you will manipulate by calling methods of "homeCell" and 
        // "freeCell" to move cards from their arrays
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1