Search code examples
oopdesign-patternsdryobject-oriented-analysis

OOP Design: Abstract class design vs Regular Inheritance


For fun, I'm designing a simple system to store records (vinyl records) data and some other general items related to records (sleeves, record cleaners, etc).

Since 90% of the data is gonna be vinyl records and the other 10% other items, I thought about separating them into two classes: Records and Items. Bear in mind that although different at the end both are products with certain things in common like price, quantity, etc.

I have doubts about whether I should create an abstract class, say Item, and two classes extending Item: Records and NonMusicalProducts. Something like this:

abstract class Item {

    static function getItemPrice($itemId, $type='record'){
        $result = 0;
        // query record table if type is record, otherwise query nonmusicalproduct table
        return $result;
    }

    abstract function getItemDetails();
}

Whereas Record:

class Record extends Item {
    static function getItemDetails($id) {
        // query DB and return record details
    }
}

and NMProduct

class NMProduct extends Item {
    static function getItemDetails($id) {
        // query DB and return NMProduct details
    }
}

Would it be ok to define both methods in NMProduct and Record as static? I'm not always gonna access that method from an object.

The other option is to have only an Item class and a Record class that would inherit from item but I have come to a point where it doesn't seem right, especially when trying to get the details:

class Item {
   function getItemDetails($id, $type){
        if ($type == 'record') {
            // query record table using id
        }
        if ($type == 'nmproduct'){
            // query nmproduct table
        }
}

It feels wrong because I think that it would be more appropriated to go to the record class to get record details since the columns would be different to those columns in nmproduct. Doing it in one parent class feels that defeat the purpose of OO.

Or the last option that I can think of, would be one single class Item:

class Item {

     function getItemPrice($id, $type) {
         // if type is record then query the record table
         // if type is nmproduct then query the nmproduct table
     }

     function getItemDetails($id, $type) {
         // if type is record then query the record table
         // if type is nmproduct then query the nmproduct table
     }
}

Again, this last option doesn't feel right since too many non-related things will be condensed in one single class. The record attributes (ie artist_id, number_of_discs, etc) are different than nmproduct.

What would be the best approach to this issue?


Solution

  • I'd create 2 classes with different getDetails and getPrice implementation:

    interface Detailable {
      public function getDetails($id);
      public function getPrice($id);
    }
    
    class Record implements Detailable{
      public function getDetails($id) {
        // custom Record Details
      }
    
      public function getPrice($id) {
        // custom Record price
      }
    }
    
    
    class NMProduct implements Detailable{
      public function getDetails($id) {
        // custom NM Product Details
      }
    
      public function getPrice($id) {
        // custom NMProduct price
      }
    }
    

    And next pass an NMProduct or Record instance into the constructor to Item:

    class Item {
    
      protected $_type;
    
      constructor(Detailable $type) {
        $this->_type = $type;
      }
    
      function getItemPrice($id) {
        return $this->_type->getPrice($id);
      }
    
      function getItemDetails($id) {
        return $this->_type->getDetails($id);
      }
    }
    

    So, real db queries are delegated from Item to concrete implementation, which is different for NMProduct and Record.