One of the first steps in my Tic-Tac-Toe program was building the board class. It was a minimal class and I stored the individual squares as a 1D array. I exposed the squares array as a public, read-only variable. Thus my game class could simply look at the desired square and check if it was empty or get its value. There wasn't an square_empty? method because I had granted access to my array.
class Board
attr_reader :squares
def initialize
@squares = Array.new(9)
end
def set_square(index, value)
@squares[index] = value
end
end
If I had wanted I could have gotten rid of set_square and replaced attr_reader with attr_accessor and the effect would have been the same. I was having an issue with this code though. My game presented options to the player for squares 1 through 9. My array indexes were 0 through 8. So when I made a move or checked a square, I had to remember to subtract 1 from the square index. This was annoying enough that I modified my set_square method to do that automatically.
class Board
attr_reader :squares
def initialize
@squares = Array.new(9)
end
def set_square(index, value)
@squares[index-1] = value
end
end
This left me with nagging issue. When I checked if a square was empty, I needed to subtract 1 from the index but not when I made a move. I realized that this inconsistency was pointless and that the only solution was to remove attr_reader. It wasn't doing anything helpful and was in fact making my game harder to work with. So I added the necessary methods to my board to replace the missing functionality and had this.
class Board
def initialize
@squares = Array.new(9)
end
def set_square(index, value)
@squares[index-1] = value
end
def empty_square?(index)
@squares[index-1].empty?
end
def square(index)
@squares[index-1]
end
end
Initially, this felt less efficient. After all, there are more lines of code doing the same functions as before. The immediate benefit I saw was that I no longer had to worry about subtracting 1 from the square index in my game class. The second benefit I didn't notice until later. This third version of my board allowed me to store the squares any way I saw fit. I could create any data structure I wanted for them. I could put them in a linked list, a hash, or even an array of square objects. This is where the usefulness of abstractions became clear to me. No matter how stored the squares now, it would not affect my game class. All I had to do was provide an interface to work with and I could store my squares on a computer at the South Pole and retrieve them over a network if I wanted.
This is a fairly simple example but I think it demonstrates the flexibility that abstractions allow. At the beginning of this project, I didn't understand why the flexibility mattered. I saw it as unnecessarily long code. It was only after a couple redesigns and multiple refactorings that I saw the benefits to me. It allowed me to make changes to how a class worked without affecting other classes that used it. It amazes me that I didn't see how useful this was at first. I guess that's one benefit of doing this apprenticeship.
No comments:
Post a Comment