2
\$\begingroup\$

This package is on pub dev here

It's on Github here

class IocContainer {
  final Map<Type, Object Function(IocContainer container)> _map;
  IocContainer(this._map);

  T get<T>() => _map[T]!(this) as T;
}

class IocContainerBuilder {
  final Map<Type, Object Function(IocContainer container)> _map = {};
  IocContainerBuilder add<T>(T Function(IocContainer container) get) {
    _map.putIfAbsent(T, () => get as Object Function(IocContainer container));
    return this;
  }

  IocContainer toContainer() => IocContainer(
      Map<Type, Object Function(IocContainer container)>.unmodifiable(_map));
}

extension Extensions on IocContainerBuilder {
  IocContainerBuilder addSingleton<T>(T service) => add((i) => service);
}

This is a Dart implementation of an Ioc container. It's so simple that I feel like I've missed something. It handles singletons and transient dependencies but you would need to handle any other kinds of life spans yourself.

Here are some tests:

import 'package:ioc_container/ioc_container.dart';
import 'package:test/test.dart';

class A {
  final String name;
  A(this.name);
}

class B {
  final A a;
  B(this.a);
}

class C {
  final B b;
  C(this.b);
}

class D {
  final B b;
  final C c;
  D(this.b, this.c);
}

class AFactory {
  final Map<String, A> _as = {};
  A get(String name) {
    final a = A(name);
    _as.putIfAbsent(name, () => a);
    return a;
  }
}

class SomeService {
  late final A a;
  late final A aa;
  SomeService(AFactory factory) {
    a = factory.get('a');
    aa = factory.get('aa');
  }
}

void main() {
  test('Basic singleton', () {
    final a = A('a');
    final builder = IocContainerBuilder();
    builder.add((i) => a);
    builder.add((i) => B(a));
    final container = builder.toContainer();
    expect(container.get<B>().a, a);
  });

  test('Basic Singleton 2', () {
    final a = A('a');
    final builder = IocContainerBuilder();
    builder.addSingleton(a);
    builder.add((i) => B(a));
    final container = builder.toContainer();
    expect(container.get<B>().a, a);
  });

  test('Method chaining', () {
final a = A('a');
final builder = IocContainerBuilder();
builder
    .addSingleton(a)
    .add((i) => B(i.get<A>()))
    .add((i) => C(i.get<B>()))
    .add((i) => D(i.get<B>(), i.get<C>()));
final container = builder.toContainer();
var d = container.get<D>();
expect(d.c.b.a, a);
expect(d.c.b.a.name, 'a');
  });

  test('Named Key Factory', () {
    final builder = IocContainerBuilder();
    builder
        .addSingleton(AFactory())
        .add((container) => SomeService(container.get<AFactory>()));

    final container = builder.toContainer();
    var someService = container.get<SomeService>();
    expect(someService.a.name, 'a');
    expect(someService.aa.name, 'aa');
  });
}

Is there anything obviously missing here? Any bugs or issues?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Is there anything obviously missing here?

No, it is that simple. I recently implemented a generalized versions of the mediator pattern and it has a similar concept (Map with Types as keys). When you start implementing these patterns yourself all the magic disappears.

Think how you will solve this:

  • You have a overall map to determine if the giving type is a Singleton/factory.
  • For the singleton you have a map with types as key's and value's is the singleton's object. If it is a factory, just new up a the instance and return it.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.