Understating abstract classes
Chris Watson
Posted on July 27, 2019
Before we begin, mandatory dictionary definition:
ab·stract [adj] /abˈstrakt,ˈabˌstrakt/
- 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 July 27, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.