Question #437MediumOOP ConceptsImportant

Abstract Class vs Concrete Class and Abstract Method vs Concrete Method

#abstract-class#concrete-class#abstract-method#oop#dart#inheritance

Answer

Overview

In Dart, classes and methods can be abstract or concrete. Understanding the difference is fundamental to writing clean, extensible object-oriented code in Flutter.

  • Abstract Class — a blueprint that cannot be instantiated directly
  • Concrete Class — a fully implemented class that can be instantiated
  • Abstract Method — a method with no body that subclasses must implement
  • Concrete Method — a method with a full implementation

Abstract Class

An abstract class is declared with the

text
abstract
keyword. It cannot be instantiated — you cannot call
text
new AbstractClass()
. It serves as a contract or template for subclasses.

dart
abstract class Animal {
  String name;

  Animal(this.name);

  // Abstract method — no body, subclass must implement
  void makeSound();

  // Concrete method — has a body, inherited as-is
  void breathe() {
    print('$name is breathing');
  }
}

// ❌ Cannot instantiate an abstract class
// final a = Animal('Lion'); // Compile-time error

Key characteristics:

  • Declared with
    text
    abstract
    keyword
  • Cannot be instantiated directly
  • Can have both abstract and concrete methods
  • Can have constructors, fields, and getters
  • Subclasses use
    text
    extends
    to inherit

Concrete Class

A concrete class is a regular, fully implemented class. It can be instantiated directly. If it extends an abstract class, it must implement all abstract methods.

dart
// Concrete class — extends abstract Animal
class Dog extends Animal {
  Dog(String name) : super(name);

  // Must implement the abstract method
  
  void makeSound() {
    print('$name says: Woof!');
  }
}

class Cat extends Animal {
  Cat(String name) : super(name);

  
  void makeSound() {
    print('$name says: Meow!');
  }
}

void main() {
  final dog = Dog('Buddy');
  dog.makeSound(); // Buddy says: Woof!
  dog.breathe();   // Buddy is breathing (inherited concrete method)

  final cat = Cat('Whiskers');
  cat.makeSound(); // Whiskers says: Meow!
}

Key characteristics:

  • No
    text
    abstract
    keyword
  • Can be instantiated with
    text
    new
    / directly
  • Must override all abstract methods if extending an abstract class
  • Can override concrete methods optionally

Abstract Method

An abstract method has no body — just a signature. It declares what must be done, not how.

dart
abstract class Shape {
  // Abstract methods — no body, no implementation
  double area();
  double perimeter();

  // Concrete method — has implementation
  void describe() {
    print('Area: ${area()}, Perimeter: ${perimeter()}');
  }
}

Rules for abstract methods:

  • Must be inside an
    text
    abstract class
  • No curly braces
    text
    {}
    and no body
  • Subclasses using
    text
    extends
    must override them
  • Classes using
    text
    implements
    must also provide implementations
dart
class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  
  double area() => 3.14159 * radius * radius;

  
  double perimeter() => 2 * 3.14159 * radius;
}

void main() {
  final c = Circle(5);
  c.describe(); // Area: 78.53975, Perimeter: 31.4159
}

Concrete Method

A concrete method has a full implementation — a body with logic. It can live in any class (abstract or concrete) and can be used or overridden by subclasses.

dart
abstract class Logger {
  // Concrete method in an abstract class
  void log(String message) {
    final timestamp = DateTime.now().toIso8601String();
    print('[$timestamp] $message');
  }

  // Abstract method — each subclass defines its own behavior
  void handleError(String error);
}

class FileLogger extends Logger {
  
  void handleError(String error) {
    // Subclass-specific implementation
    log('ERROR: $error'); // Reuses inherited concrete method
    print('Writing error to file...');
  }
}

class ConsoleLogger extends Logger {
  
  void handleError(String error) {
    log('CONSOLE ERROR: $error');
  }
}

Key characteristics:

  • Has a body
    text
    { ... }
    with implementation
  • Can live in abstract or concrete classes
  • Subclasses may optionally
    text
    @override
    it
  • Inherited automatically via
    text
    extends

Comparison Tables

Abstract Class vs Concrete Class

FeatureAbstract ClassConcrete Class
Keyword
text
abstract class
text
class
Instantiation❌ Cannot instantiate✅ Can instantiate
Abstract methods✅ Allowed❌ Not allowed
Concrete methods✅ Allowed✅ Allowed
Constructor✅ Can have (used by subclasses)✅ Yes
PurposeBlueprint / contractReady-to-use implementation
Usage
text
extends
/
text
implements
Direct instantiation

Abstract Method vs Concrete Method

FeatureAbstract MethodConcrete Method
Body❌ No body✅ Has body
text
{ ... }
Where definedOnly in
text
abstract class
Any class
Must override?✅ Yes (via
text
extends
)
❌ Optional
PurposeForces subclass behaviorProvides default behavior
KeywordNone (just no body)None

Real-World Flutter Example

dart
// Abstract class — defines the payment contract
abstract class PaymentService {
  final String currency;

  PaymentService(this.currency);

  // Abstract methods — each gateway implements differently
  Future<bool> charge(double amount);
  Future<bool> refund(String transactionId);

  // Concrete method — shared logging logic
  void logTransaction(String type, double amount) {
    print('[$type] $currency ${amount.toStringAsFixed(2)}');
  }
}

// Concrete class — Stripe implementation
class StripeService extends PaymentService {
  final String apiKey;

  StripeService({required this.apiKey}) : super('USD');

  
  Future<bool> charge(double amount) async {
    logTransaction('CHARGE', amount); // Uses inherited concrete method
    // Stripe-specific charge logic
    return true;
  }

  
  Future<bool> refund(String transactionId) async {
    logTransaction('REFUND', 0);
    // Stripe-specific refund logic
    return true;
  }
}

// Concrete class — Razorpay implementation
class RazorpayService extends PaymentService {
  RazorpayService() : super('INR');

  
  Future<bool> charge(double amount) async {
    logTransaction('CHARGE', amount);
    // Razorpay-specific charge logic
    return true;
  }

  
  Future<bool> refund(String transactionId) async {
    logTransaction('REFUND', 0);
    return true;
  }
}

void main() async {
  // Polymorphism — use any concrete class via the abstract type
  PaymentService service = StripeService(apiKey: 'sk_test_xxx');
  await service.charge(99.99); // [CHARGE] USD 99.99

  service = RazorpayService();
  await service.charge(1999);  // [CHARGE] INR 1999.00
}

When to Use

Use an abstract class when:

  • You want to share common logic (concrete methods) across subclasses
  • You need a base contract with some default implementation
  • Classes are closely related (e.g.,
    text
    Animal → Dog, Cat
    )

Use a concrete class when:

  • The class is a complete, standalone implementation
  • No subclassing is needed
  • You need to create instances directly

Use abstract methods when:

  • Every subclass must provide its own behavior
  • There is no sensible default implementation
  • You want to enforce a contract at compile time

Use concrete methods when:

  • The default behavior is shared and reusable
  • You want to provide fallback logic
  • Only some subclasses need to override

Key Rule: An abstract class can have both abstract and concrete methods. A concrete class cannot have abstract methods — every method must have a body.


Resources