user606521
user606521

Reputation: 15434

Is it possible to define "interface" that will accept class instance which type/implementation is not known ahead?

I am new to dart. In typescript I could do something like this:

// business-logic.ts

interface Item { name: string; }

interface Repository {
  addItem: (item: Item) => Promise<void>;
}

export class BusinessLogic {
  private repo: Repository;

  constructor(repo: Repository) {
    this.repo = repo;
  }

  async doSomething() {
    await this.repo.addItem({ name: 'apple' });
  }
}
// repo.ts

export class Repository {
  async addItem(item: { name: string }) { /* impl */ }
}
// runtime.ts
import { BusinessLogic } from './business-logic';
import { Repository } from './repo';

const logic = new BusinessLogic(new Repo());

So business logic can explicitly define what it needs and then actual implementation of those dependencies can be done without interchanging any types (type files).

I something like this possible in dart? Or the only option is to define abstract class Repository in business logic and then import this abstract class in repository file and make Repository class extend it?

Upvotes: 0

Views: 333

Answers (1)

Abion47
Abion47

Reputation: 24671

JavaScript being a dynamically typed language and TypeScript merely being a development-level superset of that, TypeScript interfaces aren't (and can't be) hard contracts. They are merely a scaffold for normal JavaScript objects to conform to that throw compile-time (but not runtime) errors if they fail to adhere to the definition.

Dart, on the other hand, is a strongly-typed language, so interfaces are bound to class definitions instead of arbitrary objects and with iron-clad contracts. As such, you will need to explicitly define classes that implement the interfaces before you can use them.

// business_logic.dart

abstract class IItem {
  String item;
}

abstract class IRepository {
  Future<void> addItem(IItem item);
}

class BusinessLogic {
  Repository _repo;

  BusinessLogic(this._repo);

  void doSomething() async {}
}

Here you define the interfaces as well as the BusinessLogic class. Note that Dart just uses abstract classes for this purpose, so the convention is to start the class name with "I" to differentiate interfaces from base classes. Apart from that, the only other differences are largely syntactic.

// repo.dart

class Item implements IItem {
  String item;
}

class Repository implements IRepository {
  Future<void> addItem(IItem item) async {
    /* impl */
  }
}

Here the classes get defined that implement the interfaces. Once again, the differences here are largely syntactic with the exception that Item must be explicitly defined and it must implement IItem.

(One tangential thing to note is that, in your TypeScript code, your Repository class doesn't actually reference the Repository or Item interface but instead defines a structure and an inline type restriction that just so happens to be compatible with them. While this wouldn't likely result in runtime bugs, these are typos that would need to be fixed if this were production code. Incidentally, if TypeScript were strongly-typed like Dart, this typo would be an error instead.)

// runtime.dart

import './business_logic' show BusinessLogic;
import './repo' show Repository;

final logic = BusinessLogic(Repository());

The only differences here are syntactic. Note that the show syntax is merely to match the selective imports from the TypeScript and isn't actually necessary.

Upvotes: 2

Related Questions