0
\$\begingroup\$

To get more familiar with Java, I decided to make a Tetris clone.

As I finished the Tiles beans class, I went to the Tetromino class, which is basically made of different sets of 4 Tiles objects (I'm grouping them with ArrayLists). Each Tetromino has a different colour and shape.

Would it be better practice to identify what Tetromino I'm trying to make right in the Tetromino class, or should I make a different class for each different kind of Tetromino available, probably extending the Tetromino class?


Example

(Please note that these are just design-illustrative made to represent my question)

Instead of something like:

public class Tetromino {
    private ArrayList<Tiles> tiles = new ArrayList<Tiles>();        

    public Tetromino(char shape) {
        switch(shape) {
          case 'L':
             createLShapedTetr();
             break;
          /* etcetera */
        }
    } 

    private void createLShapedTetr(){ 
     /* 
      Sets color for bricks
      Adds each brick (with their X and Y coordinates) to the arrayList
     */
    }

   /* other methods, such as spinPiece() */
}

Do something like this:

public class Tetromino {
    private ArrayList<Tiles> tiles = new ArrayList<Tiles>();

     /* Tetrominoes basic methods that every Tetr does */        
}

public class TetrominoShapeJ extends Tetromino {
     /* sets colour, space and spin-method in here */ 
}
\$\endgroup\$
0

4 Answers 4

2
\$\begingroup\$

You should only create subclasses for the different shapes if they are going to behave differently. If I were implementing it, I don't think they would behave differently, so I would not make subclasses for them. I think in my case every method would be the same, and each instance might have a different set of points with the blocks it covered. So an example method called public boolean canMoveDown might check the shapes points against any points on the game board below it, which would be the same implementation for each shape.

I don't think you should be passing a char to the constructor, though. I'd prefer an enum, or barring that, the set of points the particular shape you are creating covers, with that set kept in a factory. That keeps all of that data out of the class and the class is just responsible for processing the set of points it takes up. When coding try to follow the single responsibility principle where each class has one thing to do. This keeps everything much simpler and much easier to test and maintain.

Studies on software development have shown that systems with a large amount of inheritance are tougher to work on, tougher to maintain, and have more bugs. It's basically hidden functionality when you are looking at any class and it has a whole set of classes above it in an inheritance chain. So using inheritance for no reason is very bad.

\$\endgroup\$
1
\$\begingroup\$

From an Object Oriented perspective, it's better to create extending classes, as in your second suggestion. This makes it easier to add more implementations in the future, to override existing implementations to create even more specialized behavior, etc.

In your case, however, since there is a mathematical limit to the number of one-sided tetrominos, I think your first approach is fine.

Do you plan on making lots of different types of tetrominos, different colors, textures, or other highly customizable things? I have a feeling YAGNI.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your answer. The only customizable things that I'm planning to do are outside the Tetromino scope. \$\endgroup\$
    – streppel
    Commented Jul 31, 2013 at 17:13
1
\$\begingroup\$

I think that the principle which applies here is favour composition over inheritance.

Java provides a straightforward implementation of the lightweight pattern via enum. You can set up an enum TetrominoShape with 7 possible values:

public enum TetrominoShape {
    I,
    O,
    etc.
}

Then each Tetromino instance would have a final TetrominoShape field, a position, an orientation, etc.

I don't see any need for Tiles.

\$\endgroup\$
0
1
\$\begingroup\$

Go for static factory methods : allow creation only through static methods on your class, keep the constructor private.

(also keep that List of Tiles private)

public class Tetromino {
    private ArrayList<Tiles> tiles;

    private Tetromino(List<Tile> tiles) {
        this.tiles = new ArrayList<Tiles>(tiles)
    }

    public static Tetromino createLShaped(){ 
        // create tiles
        // call private constructor 
    }

   /* other methods... */
}
\$\endgroup\$

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.