Difference between abstract def and def

So the initialize in an abstract class, is only executed on the classes that inherit from it. It’s not actually being executed on the abstract class itself (because an abstract class) cannot instantiate itself.

Exactly. If you were to try and create an instance of Animal or Tokenizer it would not compile, initializer or not.

1 Like

Okay that seems really confusing at first, because I thought then wtf is the difference between just using a regular def, lol. I think I get it now, thanks.

Your Database adapters example really shined the light on this for me. That helped a lot.
I personally think abstract is a misnomer though, but I could see why that word is used. I don’t agree with it at all (for all languages), but that’s just a pet peeve and I’m not going to be able to change anything about that, that’s for sure.

but that’s just a pet peeve and I’m not going to be able to change anything about that, that’s for sure.

At least you know this :slight_smile: I’ll write a blog post so that I can go over abstract classes and methods in detail and hopefully help anyone else that’s struggling with them.

class Animal
  property name = "Base Animal"
  
  def initialize()
    pp "THIS IS ONLY CALLED! AND I MEAN ONLY CALLED: IF Dog.new is executed"
  end
  
end


class Dog < Animal
  def say_hello
    pp self.name
  end
end


pet = Dog.new

pet.say_hello

This still works and is perfectly valid code.

The only differences is:

  • Animal.new can be used.

Seems to me… abstract is just a way to protect a developer from doing Animal.new? (in this context). I mean, is it really worth it? The developer can still do Dog.new and it’ll work just fine. Why would a developer want to use Animal.new and say_hello in the first place, when the method is only in the Dog class?

I unfortunately still don’t understand the use case for abstract.

It feels like it hinders the developer on what classes / stuff they can instantiate. Why be more restrictive on what they can do?

Well you didn’t take advantage of abstract methods in your Animal class. That is where the real difference is. If you don’t have abstract methods you may as well not use an abstract class.

Still confused about this. You can add methods to your abstract class and it can have tons of logic.
What you are saying is, conventionally, having logic in the abstracted class is a bad idea? But people do it anyway sometimes?

You can define methods with logic, but abstract methods are methods where the extending class needs to define specific logic itself. Look back at the database adapter example. Each adapter has its own way of doing certain things, but they all need to have a similar interface. Having logic in an abstract class is just fine, as in many instances you will want to share methods among the child classes.

Hmm. classes can inherit from a main class and share logic just fine even without abstract.

The only difference I can see so far with abstract, is it literally just protects a developer from instantiating itself directly? That’s it?

You’re still missing the most important part, the abstract methods. Technically you can accomplish the same thing with a normal class by throwing a NotImplementedException in methods that are supposed to be abstract, but this provides compile time safety.

If you were to forego the abstract class you would have to duplicate a lot of functionality in each method and you wouldn’t be able to do this:

def insert(adapter : Granite::Adapter::Base, table_name, fields, params, lastval)
    adapter.insert(table_name, fields, params, lastval)
end

Instead you would have to use a Union which comes with its own issues.

1 Like

Blog post finished. Hopefully it encapsulates this conversation well enough and provides enough detail and examples.

https://dev.to/watzon/understating-abstract-classes-4ejj

@watzon

class Adapter

  def initialize(@name : String, @url : String)
  end
  # Other methods...

end

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

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


adapter1 = Mysql.new("Mysql", "localhost")

pp adapter1.insert(0, 0)

adapter2 = Postgres.new("Postgres", "localhost")

pp adapter2.insert(0, 0)

Each class that inherits Adapter still can use their own insert logic.

abstract, just makes it so Adapter cannot be instantiated directly. Which IMO, is restricting the developer. Sorry, I just don’t understand the use case for abstraction, or think it’s beneficial.

If anything, it makes syntax more verbose, and adds artificial limitations to what the developer can do. A developer should have the ability/right to instantiate any class they please.

It’s not just restricting the developer, it’s providing a base class that can be used in type restrictions. The base class can also implement methods which can be used in all the base classes. I really don’t know how to be any clearer.

Abstract classes are very much like Interfaces in Rust, as are modules. Both have different use cases though.

You don’t need to use abstract to achieve this though (my example above)

No you don’t, and in an untyped language you would probably do exactly that, or define the method insert in the Adapter class with a raise "Not implemented in the body. But in a typed language like crystal you get added benefits using abstract classes. The compiler can yell at you if you’re missing methods that you need or of the methods don’t have the correct signature. You won’t get that with your way.

The compiler will yell at you if you’re missing methods even without abstract, won’t it?

If that is a feature/benefit of abstract that a non abstracted class does not have, I could see that as a benefit. About the only benefit though. But at that point, I’d argue the compiler should yell at you regardless of a abstract keyword.

It won’t. It will yell at you if you try to use a method that doesn’t exist, but that’s not the same thing.

Thank you for this, I was about to follow up on gitter after learning a bit more about C++ abstract (coincidentally on a podcast) this morning. I think I more or less get it now, referring to it as a way to avoid a union is what helped make it click. Sometimes I just need context for what classic problem is being solved or else it seems superfluous to me. Would it be good policy for me to make my most generic classes abstract if I never expect to instantiate them or am I gonna get in over my head if I’m not ready?

Try creating an array of adapters, inserting two types of adapters, then picking one and calling insert on it. What happens? And how do you fix the problem? Also, does it make sense to instantiate Adapter and not just one of its subclasses?

By the way, abstract methods are not needed in Crystal. They are just a way to document things in a better way, and to give an error earlier (instead of when you actually use the method).

2 Likes

Thank you for that as well. So far I approach it all like Ruby with Typing, but once we’ve settled into a style guide I bet a lot of things like this will seem more natural and it will be more apparent what’s functionality and what’s sugar.