Creating an interface for building

Several times now I come across a use case when I need to define an interface for how the classes themselves are created. One such example might be if I want to create an interface class that defines an interface with which objects can be serialized and non-serialized (entering data into a database sent as JSON, etc.). You could write something like this:

abstract class Serializable { String serialize(); Serializable unserialize(String serializedString); } 

But now you have a problem, since serialize() is the proper instance method, and unserialize() should be the static method (which is not inherited or not used by the interface) or the constructor (which also isnโ€™t inherited).

This leaves a state in which classes that implement the Serializable interface are needed to define the serialize() method, but there is no way to require these classes to define a static unserialize() constructor or a Foo.fromSerializedString() constructor.

If you create an instance method unserialize() , then the non-esterization of the Foo implementation class will look like this:

 Foo foo = new Foo(); foo = foo.unserialize(serializedString); 

which is rather bulky and ugly.

The only other option I can think of is to add a comment to the Serializable interface, which understands that implementation classes define the appropriate static method or constructor, but this is obviously error-prone if the developer skips it, and the code termination hurts.

So, is there a better way to do this? Is there some kind of template with which you can have an interface that forces the implementation of classes to determine how to create yourself or something that gives a general effect?

+6
source share
4 answers

You will need to use instance methods if you need inheritance guarantees. You can do a little better than manually instantiating 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 this instance:

 var foo = Serializable.fromSerializedString(Foo, 'someSerializedString'); 

It may be a little prettier and more natural than the manual method, but keep in mind that it uses reflection with all the problems that may arise.

If you decide to go with a static method and a warning comment instead, it might also be useful to provide a custom Transformer that scans all classes that implement Serializable and warn the user or stop the build if they don't have the appropriate static method or unserialize constructor (similar to how Polymer does things). This, obviously, would not provide instant editor feedback with instance methods, but would be more noticeable than a simple comment in documents.

+3
source

I think this example is a more Dart-like way to implement encoding and decoding. In practice, I donโ€™t think that โ€œenforcingโ€ a decoding signature will actually help to catch errors or improve the quality of the code. If you need to connect decoder types, you can configure the decoders.

 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}'); } 
+1
source

I suggest you define the unserialize function as a named constructor like this:

 abstract class Serializable<T> { String serialize(); Serializable.unserialize(String serializedString); } 

This eliminates the need for static methods. A possible implementation might 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 this:

 JsonMap m = new JsonMap(); m.map = { 'test': 1 }; print(m.serialize()); JsonMap n = new JsonMap.unserialize('{"hello": 1}'); print(n.map); 

During testing, I noticed that Dart will not make any mistakes unless you really implement the methods that your promises class implements using. It might just be an ic with my local Darth.

0
source

A possible template that I came up with is to create a Factory class that uses instance methods in a slightly less inconvenient way. Something like the following:

 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 specific 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 built with

 Foo foo = new ModelFactory.fromSerializedString(Foo.constructor, serializedString); 

The result of all this is that it ensures that each particular class has a method that can create a new instance of itself from a serialized string, and also has a common interface that allows you to call this method from a static context. It still creates an additional object whose purpose is to move from a static context to an instance and then drop it, there are many other overheads, but at least all this ugliness is hidden from the user. However, I am not yet convinced that this is the best way to achieve this.

0
source

Source: https://habr.com/ru/post/980050/


All Articles