d3vkit
d3vkit

Reputation: 1982

Change polymorphic type in ruby

(I am using the gosu gem to draw to the screen.)

I am working on a file tree in ruby (not rails). Right now I have something like this:

class FileTree
  @files = []
end

class Folder
  @assets = []

  def left_click
    # Open or close
  end
end

file_tree = FileTree.new
folder1 = Folder.new
folder2 = Folder.new
file_tree.files << folder1 << folder2

Now the folders get drawn to the screen, and the user should be able to click them to open/close them, and view the assets that are inside. I am not sure the best way to handle this change of state.

My first thought was to have a bool @opened, and just check that to determine if assets are @visible (another bool). However, I would like to replace the conditionals with polymorphism.

So I made ClosedFolder and OpenFolder classes that inherit from Folder, and then just initialized ClosedFolders into my FileTree. However, now I need to click the folder and have it change to an OpenFolder - but the file_tree doesn't know about the changed object and I have basically had to do a weird swap to get it in place.

I then tried adding @type to Folder, making modules of ClosedFolder and OpenFolder, and then making methods that do this:

class Folder
  include ClosedFolder, OpenFolder

  def left_click
    public_send("#{@type}_left_click")
  end
end

module ClosedFolder
  def closed_folder_left_click
    @type = :open_folder
    @assets = get_files
  end
end

module OpenFolder
  def open_folder_left_click
    @type = :closed_folder
    @assets = []
  end
end

When I ran this code, my CPU shot through the roof. I also don't like naming every method after the module/type.

So, how do I handle this problem? Are conditional checks the way to go? Would love to be able to just call left_click on my asset and have it use the method in the correct object.

Upvotes: 1

Views: 212

Answers (2)

d3vkit
d3vkit

Reputation: 1982

After some more thought, this just wants to be a state_machine, which is nearly like a case or branching ifs. However, we were able to implement a way to handle this without conditionals that feels pretty good:

class Folder
  def initialize(name)
    @name   = name
    @state  = ClosedFolderState.new(self)
  end

  attr_reader :name
  attr_accessor :state

  def pretty_name
    "#{@state.icon} #{@name}"
  end

  def left_click
    @state.left_click
  end
end

class ClosedFolderState
  def initialize(folder)
    @folder = folder
  end

  def icon
    '+'
  end

  def left_click
    @folder.state = OpenFolderState.new(@folder)
  end
end

class OpenFolderState
  def initialize(folder)
    @folder = folder
  end

  def icon
    '-'
  end

  def left_click
    @folder.state = ClosedFolderState.new(@folder)
  end
end

This may be overkill, but this seems to meet our goal of just seeing if we can move away from conditionals, and here using a decorator seems to work. I am not saying this would be everyone's preferred solution, but I think this answers my question.

Upvotes: 1

SteveTurczyn
SteveTurczyn

Reputation: 36860

Just seems like a very complicated way to avoid some simple conditionals.

If it were my project I'd be going down this route...

class Folder

  def initialize
    @assets = []
    @open = false
  end

  def left_click
    @open = !@open
    @assets = @open ? get_files : []
  end

end

Upvotes: 1

Related Questions