Say I have a package that exposes a mixin that provides some extensibility through an API:
mixin ListenerModifier<T> {
T get value;
void addListener(Callback callback);
void removeListener(Callback callback);
}
Say I want to make a "plugin" based on this mixin:
mixin PreviousValue<T> on ListenerModifier<T> {
late T previous;
const PreviousValue() {
addListener(() => previous = value);
}
}
(You could also have plugins defined in other packages as well, perhaps to expose a Stream<T> getter.)
Then, a user could use any plugins at will, like this:
abstract class MyListener<T> with ListenerModifier<T>, PreviousValue<T> {}
The issue is, mixins can't have constructors like the above PreviousValue assumes. Is there some OOP/architectural way to get around this problem? I initially thought of just forcing users of PreviousValue (for example) to call a registerPreviousValue() method in their MyListener constructor as a workaround, but that is highly error prone.
Related:
How to group mixins in Dart?
How can I initialize a mixin's immutable data in Dart?
Just realized for the particular example I gave, I can use a different sort of plugin system without mixins:
mixin ListenerModifierPluginAPI {
T get value;
void addListener(Callback callback);
void removeListener(Callback callback);
}
abstract class ListenerModifier with ListenerModifierPluginAPI {
T registerPlugin<T>(T Function(ListenerModifierPluginAPI) plugin) => plugin(this);
}
class MyListenerModifier extends ListenerModifier {
late final PreviousValueState previousValueState;
const MyListenerModifier() {
previousValueState = registerPlugin(previousValuePlugin);
}
// ...
}
This may not work for all people though. If someone has a different solution, I am all ears.
I have such an example of a model:
class BirthdayModel {
List birthdays;
BirthdayModel({
#required this.birthdays,
});
factory BirthdayModel.fromJson(json){
return BirthdayModel(birthdays: json['data']);
}
Map<String, dynamic> toJson() {
return {
'birthdays': birthdays,
};
}
}
I want to transfer multiple models into one method:
exampleMethod(model: BirthdayModel);
and then in this method call the constructors or methods of the passed class
exampleMethod(#required model){
return model.fromJson(data);
}
Is it possible to do this?
Not the way you write it.
You cannot pass a class as argument. Even type arguments only pass types, so you cannot use static members that way.
What you can do is:
T exampleMethod<T>(T createModelFromJson(dynamic json)){
return createModelFromJson(data);
}
and call it as :
var birthday = exampleMethod(BirthdayModel.fromJson);
There is no way to access the fromJson programmatically - it's not an instance method so there is no interface for it. Static methods must be accessed explicitly.
(I'm ignoring dart:mirrors because you probably won't have access to those).
A few times now I've run into a use case where I need to define an interface for how classes construct themselves. One such example could be if I want to make an Interface Class that defines the interface by which objects can serialize and unserialize themselves (for input into a database, to be sent as JSON, etc). You might write something like this:
abstract class Serializable {
String serialize();
Serializable unserialize(String serializedString);
}
But now you have a problem, as serialize() is properly an instance method, and unserialize() should instead be a static method (which isn't inheritable or enforced by the Interface) or a constructor (which also isn't inheritable).
This leaves a state where classes that impliment the Serializable interface are required to define a serialize() method, but there is no way to require those classes to define a static unserialize() method or Foo.fromSerializedString() constructor.
If you make unserialize() an instance method, then unserializing an implementing class Foo would look like:
Foo foo = new Foo();
foo = foo.unserialize(serializedString);
which is rather cumbersome and ugly.
The only other option I can think of is to add a comment in the Serializable interface asking nicely that implementing classes define the appropriate static method or constructor, but this is obviously prone to error if a developer misses it and also hurts code completion.
So, is there a better way to do this? Is there some pattern by which you can have an interface which forces implementing classes to define a way to construct themselves, or something that gives that general effect?
You will have to use instance methods if you want the inheritance guarantees. You can do a bit nicer than manual instantiation though, by using reflection.
abstract class Serializable {
static Serializable fromSerializedString(Type type, String serializedString) {
ClassMirror cm = reflectClass(type);
InstanceMirror im = cm.newInstance(const Symbol(''), []);
var obj = im.reflectee;
obj.unserialize(serializedString);
return obj;
}
String serialize();
void unserialize(String serializedString);
}
Now if someone implements Serializable they will be forced to provide an unserialize method:
class Foo implements Serializable {
#override
String serialize() {
// TODO: implement serialize
}
#override
void unserialize(String string) {
// TODO: implement unserialize
}
}
You can get an instance like so:
var foo = Serializable.fromSerializedString(Foo, 'someSerializedString');
This might be a bit prettier and natural than the manual method, but keep in mind that it uses reflection with all the problems that can entail.
If you decide to go with a static method and a warning comment instead, it might be helpful to also provide a custom Transformer that scans through all classes implementing Serializable and warn the user or stops the build if any don't have a corresponding static unserialize method or constructor (similar to how Polymer does things). This obviously wouldn't provide the instant feedback the an editor could with instance methods, but would be more visible than a simple comment in the docs.
I think this example is a more Dart-like way to implement the encoding and decoding. In practice I don't think "enforcing" the decode signature will actually help catch bugs, or improve code quality. If you need to make the decoder types pluggable then you can make the decoders map configurable.
const Map<String,Function> _decoders = const {
'foo': Foo.decode,
'bar': Bar.decode
};
Object decode(String s) {
var obj = JSON.decode(s);
var decoder = _decoders[obj['type']];
return decoder(s);
}
abstract class Encodable {
abstract String encode();
}
class Foo implements Encodable {
encode() { .. }
static Foo decode(String s) { .. }
}
class Bar implements Encodable {
encode() { .. }
static Foo decode(String s) { .. }
}
main() {
var foo = decode('{"type": "foo", "i": 42}');
var bar = decode('{"type": "bar", "k": 43}');
}
A possible pattern I've come up with is to create a Factory class that utilize instance methods in a slightly less awkward way. Something like follows:
typedef Constructable ConstructorFunction();
abstract class Constructable {
ConstructorFunction constructor;
}
abstract class Serializable {
String serialize();
Serializable unserialize(String serializedString);
}
abstract class SerializableModel implements Serializable, Constructable {
}
abstract class ModelFactory extends Model {
factory ModelFactory(ConstructorFunction constructor) {
return constructor();
}
factory ModelFactory.fromSerializedString(ConstructorFunction constructor, String serializedString) {
Serializable object = constructor();
return object.unserialize(serializedString);
}
}
and finally a concrete implementation:
class Foo extends SerializableModel {
//required by Constructable interface
ConstructorFunction constructor = () => new Foo();
//required by Serializable interface
String serialize() => "I'm a serialized string!";
Foo unserialize(String serializedString) {
Foo foo = new Foo();
//do unserialization work here to populate foo
return foo;
};
}
and now Foo (or anything that extends SerializableModel can be constructed with
Foo foo = new ModelFactory.fromSerializedString(Foo.constructor, serializedString);
The result of all this is that it enforces that every concrete class has a method which can create a new instance of itself from a serialized string, and there is also a common interface which allows that method to be called from a static context. It's still creating an extra object whose whole purpose is to switch from static to instance context, and then is thrown away, and there is a lot of other overhead as well, but at least all that ugliness is hidden from the user. Still, I'm not yet convinced that this is at all the best way to achieve this.
I suggest you define the unserialize function as named constructor like so:
abstract class Serializable<T> {
String serialize();
Serializable.unserialize(String serializedString);
}
This eliminates the need of static methods.
A possible implementation could look like this:
import 'dart:convert';
class JsonMap implements Serializable<JsonMap> {
Map map = {};
JsonMap() {
}
String serialize() {
return JSON.encode(map);
}
JsonMap.unserialize(String serializedString) {
this.map = JSON.decode(serializedString);
}
}
You can (de)serialize like so:
JsonMap m = new JsonMap();
m.map = { 'test': 1 };
print(m.serialize());
JsonMap n = new JsonMap.unserialize('{"hello": 1}');
print(n.map);
While testing this, I noticed that Dart will not throw any errors at you if you dont actually implement the methods that your class promises to implement with implements. This might just be a hicc-up with my local Dart, though.
Suppose you have:
class Schema {
Schema.fromText(String jsonString) {
...
}
}
In this constructor, assume there is an URL provided in the jsonString to download data and the only API to read an URL is one that returns a Future. Also, assume Schema is only a valid object when that URL data has been read and processed. Is it possible to even implement ...?
What you want to do is not possible with standard constructors.
Instead, try a static method that returns a new instance wrapped in a Future.
Something like:
class Schema {
Schema._fromApi(String apiResults) { ... }
static Future<Schema> build(String jsonString) {
return getContentsOfUrl(jsonString['url'])
.then((contents) => new Schema._fromApi(contents));
}
}
Many methods like complete in class Completer are marked "abstract", but in fact It can be directly invoked without being implemented. I'm really confused. Could anyone help me?
Yes, this can be a bit confusing. While abstract classes cannot be instantiated, it is possible to make them appear to be instantiable by defining a factory constructor. This is what Completer, Future and other abstract classes do:
abstract class Completer<T> {
factory Completer() => new _CompleterImpl<T>();
...
}
You can then invoke methods on the object created by the factory constructor. In the example above, factory Completer() returns a new _CompleterImpl object. Look at the (truncated) code of that class:
class _CompleterImpl<T> implements Completer<T> {
final _FutureImpl<T> _futureImpl;
_CompleterImpl() : _futureImpl = new _FutureImpl() {}
Future<T> get future {
return _futureImpl;
}
void complete(T value) {
_futureImpl._setValue(value);
}
...
}
and you see complete(); that is the method being invoked.