DZone
Web Dev Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Web Dev Zone > Building Reusable Object-Oriented Systems: Inheritance

Building Reusable Object-Oriented Systems: Inheritance

When building software, we often come across special cases, specializations, and shared logic. In object-oriented languages, inheritance is commonly used to deal with these.

Joel Quenneville user avatar by
Joel Quenneville
·
Nov. 29, 16 · Web Dev Zone · Tutorial
Like (0)
Save
Tweet
3.19K Views

Join the DZone community and get the full member experience.

Join For Free

When building software, we often come across special cases, specializations, and shared logic. In object-oriented languages, inheritance is commonly used to deal with these.

Building an API Client

Let’s say you are writing a client to interact with a third-party API that lists movies. It might look like:

module MovieFacts
  class Client
    def initialize(client_id, client_secret)
      @client_id = client_id
      @client_secret = client_secret
    end

    def directors
      fetch_data("/directors").map { |director| Director.new(director) }
    end

    def director(name)
      Director.new(fetch_data("/directors/#{name}"))
    end

    private

    def fetch_data
      # fetch directors from API
    end
  end
end

The client returns a lightweight Director object based on the JSON response.

module MovieFacts
  class Director
    def initialize(json)
      @raw_data = JSON.parse(json)
    end

    def name
      @raw_data.fetch("name")
    end

    def id
      @raw_data.fetch("id")
    end
  end
end

Inheritance 

A new set of requirements come in. movie-facts.com is rate limiting your service so you need to watch how many requests you make in a day. The good news is that you know that movie-facts.com only updates its systems once a day so it should be trivial to cache the data in memory and only fetch from the API once a day. Not only does this fix your rate-limiting issues but it also speeds up performance of your own system.

There are now two ways of doing the same task (and it’s not too hard to imagine others coming along down the road). Creating a separate class would result in a lot of duplicated code.

Duplicated code

The intro to “Design Patterns in Ruby” describes an additional principle:

Separate things that change from those that stay the same

The logic for returning the list of director objects remains the same, but the way we fetch the data changes based on context. We need a way to separate the fetching logic (which changes), from the director logic (which doesn’t).

The most commonly used solution for this problem is inheritance. Inheritance is a way of creating a specialized form of an object. Because of this, child classes have access to all the methods and private state of their parent. They are their parent, with a few modifications.

Extracting a base class

You start by extracting the shared logic into a ClientBase class

module MovieFacts
  class ClientBase
    NotImplementedError = Class.new(StandardError)

    def initialize(client_id, client_secret)
      @client_id = client_id
      @client_secret = client_secret
    end

    def directors
      fetch_data("/directors").map { |director| Director.new(director) }
    end

    def director(name)
      Director.new(fetch_data("/directors/#{name}"))
    end

    private

    def fetch_data
      raise NotImplementedError
    end
  end
end

You then derive a class for handling fetches via HTTP:

module MovieFacts
  class HttpClient < ClientBase

    private

    def fetch_data
      # make HTTP request
      # cache response
    end
  end
end

And one for fetches from the cache:

module MovieFacts
  class CacheClient < ClientBase

    private

    def fetch_data
      # read data from cache
    end
  end
end

Now if you ever want to add a third way to fetch the data in the future, all you need to do is add a new sub-class. Great use of the Open/Closed principle!

Limitations of Inheritance 

Simple inheritance solved our duplicated logic problem. It also made it easy for us to extend the system to fetch data from other sources if necessary. However, it does have a few critical weaknesses when it comes to building reusable OO components:

  1. it is vulnerable to combinatorial explosion when there are multiple independent parts of the code that vary.
  2. there is no encapsulation between parents and descendants.

We dig into how to solve these problems in the next posts on modules and composition.

Further Reading 

This article is part 1 of 4 in a series on building reusable object-oriented software.

Inheritance (object-oriented programming)

Published at DZone with permission of Joel Quenneville, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Quantum Computers Explained
  • How To Evaluate Software Quality Assurance Success: KPIs, SLAs, Release Cycles, and Costs
  • Modern Application Security Requires Defense in Depth
  • Common Types Of Network Security Vulnerabilities In 2022

Comments

Web Dev Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo