loading...

Understating abstract classes

watzon profile image Chris Watson ・3 min read

Before we begin, mandatory dictionary definition:

ab·stract [adj] /abˈstrakt,ˈabˌstrakt/

  1. existing in thought or as an idea but not having a physical or concrete existence.

Abstractions are an extremely helpful, albeit difficult to understand concept in programming. After all, if abstractions were easy to understand they probably wouldn't be abstract... Would they?

Abstract classes are just one form of abstraction that exists in programming, and the one I'll be going over in this post. This was inspired by a question from @girng on the crystal-lang forum. Since the original question was a Crystal question, and since Crystal is currently my favorite language I will be using Crystal examples, but a lot of the concepts transfer to other languages, even if the implementation is different.

What are abstract classes?

Abstract classes are classes which are meant to be inherited, but not instantiated. They act as a base for other classes by providing methods that are should exist on all child classes without actually implementing those methods.

An example of a simple abstract class is as follows:

abstract class Foo
  abstract def bar(text : String) : Array(String)
end

In this case we have a class Foo which has an abstract method bar. The bar method must accept a single string parameter and must return a string array. Note that if we try and create an instance of the Foo class the compiler will throw an error. If we want Foo to be useful we have to extend it.

class Baz < Foo
  def bar(text : String)
    text.split(" ")
  end
end

Now we've defined a class Baz which implements the bar method exactly as described. Let's try and make the compiler throw an error though.

# This will throw an error since `bar` is not defined on `Baz`
class Baz < Foo
end

# This will throw an error since `bar` has the incorrect definition
class Baz < Foo
  def bar(opts : Array(String))
    opts.join
  end
end

Abstract classes can also define actual methods to be included in child classes. Those methods can be initializers or any other type of method.

class Foo
  def initialize
    puts "#{self.class} initalized"
  end

  abstract def bar(text : String) : Array(String)
end

class Baz < Foo
  def bar(text : String)
    text.split(" ")
  end
end

baz = Baz.new
# => "Baz initalized"

Where should I use them?

Abstract classes are useful in a number of situations, but the number one example that springs to mind is with adapters, such as for different databases. Databases are a good example because they all have similar functionality, but they all do things in a slightly different way.

Here is a super basic example:

class Connection
  # Implementation
end

abstract class Adapter

  def initialize(@name : String, @url : String)
  end

  abstract def insert(table, fields) : Int64

  # Other methods...

end

class Mysql < Adapter
  def insert(table, fields)
    # Insert stuff
    0
  end
end

class Postgres < Adapter
  def insert(table, fields)
    # Insert stuff
    0
  end
end

Obviously this is not a functional example, but it should compile and illustrates the basic concept. For a working example you can check out Granite::Adapter::Base from amberframework/granite.

Thanks for reading this. Please don’t forget to hit one of the the Ego Booster buttons (personally I like the unicorn), and if you feel so inclined share this to social media. If you share to twitter be sure to tag me at @_watzon.

Some helpful links:
https://crystal-lang.org/
https://github.com/kostya/benchmarks
https://github.com/kostya/crystal-benchmarks-game
https://github.com/crystal-lang/crystal

Find me online:
https://medium.com/@watzon
https://twitter.com/_watzon
https://github.com/watzon
https://watzon.tech

Posted on by:

watzon profile

Chris Watson

@watzon

Full stack dev with a hard on for open source

Discussion

markdown guide
 

Do you have another example different than "Foo" "Bar"?
I'm a total newbie, and would like to have a more relatable example.
Much appreciated.