views:

111

answers:

5

Imagine the following model:

  • A Table has many Rows
  • A Row has many Cells

What would be the preferable interface to deal with these classes in a "object oriented way"?

1 - Provide access to the properties rows / cells (Not necessarily exposing the underlying data structures, but creating for example a class RowCollection...)

my_table = new Table()
my_table.rows.add([1,2,3])
my_row = my_table.rows.get(0)
my_row.cells.get(0)
for(cell in my_row.cells) {}
...

2 - Or provide the methods directly in the Table and Row classes

my_table = new Table()
my_table.add_row([1,2,3])
my_row = my_table.get_row(0)
my_row.get_cell(0)
for(cell in my_row.get_cells) {}
...

3 - None of the above...

A: 

It depends on how you intend to access the Rows/Cells.

There is no one correct way of doing it - you need to decide how you want to access them and build your objects to expose them in the way you want to consume them.

Oded
A: 

It depends a lot on the data and what you plan to do with it. Will users want individual cells? Individual rows/columns? Subsections of rows/columns?

Probably the cleanest way is to provide functor interfaces. Provide one or more functions that will run the functors on every element, or on a subset defined in the functor.

That may work less well if users need to access complex combinations of cells.

Michael J
+1  A: 

I think that the answer is largely subjective. If we go by your example, providing methods or properties of your class to return a value by a row/column reference might be appropriate. These could be implemented concurrently, eg:

myClass.Row[x].Column[y]    
myClass.Column[y].Row[x]    
myClass.Cell[x,y]

You might also decide that it is better to expose a list directly, if the data "rows" are finite:

myClass.SomeArrayOfValues[itemIndex]

I notice you using phrases like "tables" and "rows", so I might assume you wish to have your class represent a database or similar structure, but you might find that while it may be efficient to store the data that way, you might find that exposing the data in another form may make more sense to the user of your class.

In the end, how you choose to do this should really be designed to reflect the purpose of the data itself and the system you are modelling, and that can only be decided on a case-by-case basis.

S.Robins
The objective is to represent a basic spreadsheet structure
Miguel Fonseca
That being the case, if it made sense for you to do so, you might even expose a method or property that uses a String from your application which uses standard spreadsheet cell notation (eg: "A1", "D3", etc...). This would likely be best however as an extension or wrapper for something along the lines of the examples I gave in my answer above.
S.Robins
+1  A: 

Based on your comment regarding the usage, "The main use case is adding, sorting and iterating the values," I would probably not allow individual elements to be retrieved, but instead have the user provide a functor to act upon the stored elements. Expressed in C++.

class Table
{
public:
    Table();
    //Table(unsigned int numberOfRows, unsigned int numberOfCells);

    void addRow();
    void addCell();

    //Throw exception if out of range or don't supply these functions if not needed by user.
    void removeRow(unsigned int rowNumber);
    void removeCell(unsigned int rowNumber, unsigned int cellNumber);

    //Iterate over entire table
    template<class Pred>
    void forEach(Pred pred);

    //Iterate over a specific row, throw exception if row is out of range.
    template<class Pred>
    void forEach(unsigned int row, Pred pred);
}

You will have to tailor the add/update/remove calls based on how you plan on inputting/updating data. This design is strongly oriented towards manipulating collections of elements. The positives of this design is that you are not committing your user to the specific underlying structure of how you are representing the Table. This is in keeping with the Law of Demeter.

If you need to access specific individual elements, you will want a different approach or make it an extension of what's already provided here.

FP
+1  A: 

Consider how many getters and setters you can get rid of. A robust OO design has objects exporting behavior to each other, not data. For example, the skeleton of a getter/setter model of a Person:

class Person:
  def set_name(value):
  def get_name:
  def set_age(value):
  def get_age:
  def set_drink(value):
  def get_drink:
  def set_favorite_drink(value):
  def get_favorite_drink:

And here's some (pseudo-)code that uses Person:

def order_drink(person)
  if person.age >= 21:
    puts "#{person.name} can have a drink"
    person.drink = bar.order(person.favorite_drink)
  else:
    puts "#{person.name} cannot drink (legally)."

Here's how you can have a person with no getters or setters involved in ordering a drink:

class Person:
  def order_drink_from(bar):
    if self.age >= 21:
      puts "#{self.name} can have a drink"
      self.drink = bar.order(favorite_drink)
    else:
      puts "#{self.name} cannot drink (legally)"

used like this:

person.order_drink_from(bar)

I won't say that you'll never need getters in an OO program. But I will say this: setters, especially, ought to make you rethink the design. And every time you write either a getter or a setter, let a little voice in the back of your head ask you if there's a way to have the objects export behavior rather than data.

Wayne Conrad