Dart supports all the features for Object-oriented programing paradigm like Classes, Inheritance, Interfaces, Polymorphism, etc.

Dart supports all the features for Object-oriented programing paradigm like Classes, Inheritance, Interfaces, Polymorphism, etc. Inheritance in Dart might seem a little weird but apart from that, everything is all right.
A class without constructor
We use class keyword to create a class in Dart. Static Variables which live on the class should be marked with static keyword. Instance Variables and Instance Methods live on the object. this keyword inside an instance method points to the object itself, hence we can access a property of the object.

We have used toString method which overrides the same method defined in the Object class. This method (as discussed before) is used to represent an object in String format for string interpolation.
💡 We can also have static methods which should be marked with static keyword like static void myStaticMethod. These methods like static variables will be accessible on the class and can be used as factory functions.
We have marked it with @override annotation to denote that it overrides the method of the auto-inherited Object class.
💡 The intent of the @override notation is to catch situations where a superclass renames a member, and an independent subclass which used to override the member, could silently continue working using the superclass implementation.
A class with a constructor
A constructor is an instance method that is invoked when an object is created from the class. This is a good place to initialize instance variables. A constructor function has the same name as the class.

In the above example, our constructor function Person needs two required positional arguments while age has a default 18 value. Dart provides a shorthand syntax for a constructor function to set instance variables.
Person( this.firstName, this.lastName, [ this.age = 18 ] );
This is the preferred way to write a constructor whose only job is to set initial values to the instance variables. Optional parameters must have a default value, else null will be set by the constructor even if the default value was given in instance variable declaration in the class.
💡 While creating an object from a class, new keyword is optional in Dart v2.
Named constructors
Dart provides multiple constructors on a class. Apart from default constructors, other constructors must have a name. While creating an object from a class, we need to use the name named constructor.

Apart from the name, there is no difference between default and named constructors. We can also use the shorthand instance variable assignment syntax for the named constructors as well.
Getters and Setters
A combination of the getter and the setter methods are used to transform and/or encapsulate instance variables. In Dart, the getter is an instance method specified by get keyword. This method does not take any arguments, hence it does not contain parentheses (). While the setter method is specified by set keyword which receives value to be set as an argument.

In the above example, a getter can also be written using Fat Arrow function syntax like below but without any arguments.
String get fullName => "${ this._firstName } ${ this._lastName }";
💡 Dart does not have access modifiers like public, private and protected. Adding underscore as a prefix to an identifier makes it visible only inside its library.
Inheritance
A class has a constructor whose job is to set up initial instance variables. A class can also have methods. At times, some of our classes have common methods and common instance variables. Instead of maintaining two classes which have common functionalities, we can have one class inherit the functionality of the other class.

💡 If a super-class (the one we extends to) default constructor does not take any arguments, we can avoid :super() call in the sub-class. It will be called implicitly by the Dart when an object is created from the sub-class.
If we do not want a parent class to be instantiated or created objects from, we can make it abstract using abstract keyword.
When we inherit a class, its methods are also available on the sub-class. We have learned that super() function calls the super-class constructor and super keyword points to the super-class. We can also call super-class instance methods using super.superClassMethod() syntax.

When we create a method on a sub-class with the name same as on the super-class, it is called method overriding. This is because when this method is invoked on the sub-class object, method of the sub-class object is used.
💡 We can completely override the super-class method without utilizing it on the sub-class unlike in the above example, but we should avoid doing it. Instead, we should go for a different method name since it hold any relations with the method with the same name of the super-class.
Interfaces
An interface is a blueprint or a template of a class. This blueprint contains definitions of instance variables and instance methods a class must implement. This is useful to enforce a consistent class structure.
Dart implicitly defines a class as an interface, hence called as implicit interface. To force a class to implement an interface, we use implements keyword just like extends keyword.

There are a lot of things going in the above example. The important thing to remember is that an interface is just a class and we use implements keyword to enforce a class to declare relevant variables and methods.
An abstract class can not be instantiated, hence we can declare methods without a body as we did it with getFullName method in the Person class. However, an abstract class can be inherited. Hence, any class that inherits an abstract class must override the method that lacks the body.
💡 Unlike inheritance where a sub-class can extend only one super-class, a class can implement multiple interfaces like class A implements B, C, D {...}.
Polymorphism
Consider a situation where you have a method that takes an object of type Person or object of the type Employee which inherits from Person class. What Data Type of the parameter would you choose?
You could choose dynamic which represents everything but then you need to throw an exception if the argument is not of Person or Employee type. This is where Polymorphism saves us.
Polymorphism is a concept in OOP which means one object can have many forms. In a technical sense of statically typed languages like Dart means, a variable has the type of its class or any class that it inherits from.

In the above example, the object e has a type of Employee since it is an instance of that class. But it also has Person type because Employee inherits from the Person class. Hence the function fullName can accept p as well as e because they both satisfy the parameter of type Person.
💡 Similar to the case of inheritance, if a class implements an interface, any object that class also has that interface type. We can see the actual Data Type of an object using object.runtimeType syntax.