Clean up a few things in the record_replay library: (#22)

  - use closures rather than subclasses to reduce some code in recording_file.dart
  - `basename` and `dirname` were missing from `RecordingFileSystemEntity`
  - `RecordingFileSystemEntity` didn't need to use two parameterized types.
    That was an artifact of it having forked from `ForwardingFileSystemEntity`
  - Change the name pattern of the blob files to use the basename of the
    `RecordingFile` rather than the name of method being recorded
diff --git a/lib/src/backends/record_replay/encoding.dart b/lib/src/backends/record_replay/encoding.dart
index 1f5037b..412b63e 100644
--- a/lib/src/backends/record_replay/encoding.dart
+++ b/lib/src/backends/record_replay/encoding.dart
@@ -5,7 +5,6 @@
 import 'dart:convert';
 
 import 'package:file/file.dart';
-import 'package:file/src/io.dart' as io;
 import 'package:path/path.dart' as p;
 
 import 'common.dart';
@@ -118,7 +117,7 @@
 /// During replay, this allows us to tie the return value of of one event to
 /// the object of another.
 String _encodeFileSystemEntity(
-    RecordingFileSystemEntity<FileSystemEntity, io.FileSystemEntity> entity) {
+    RecordingFileSystemEntity<FileSystemEntity> entity) {
   return '${entity.runtimeType}@${entity.uid}';
 }
 
diff --git a/lib/src/backends/record_replay/recording_directory.dart b/lib/src/backends/record_replay/recording_directory.dart
index 0686141..52334b1 100644
--- a/lib/src/backends/record_replay/recording_directory.dart
+++ b/lib/src/backends/record_replay/recording_directory.dart
@@ -12,8 +12,7 @@
 
 /// [Directory] implementation that records all invocation activity to its file
 /// system's recording.
-class RecordingDirectory
-    extends RecordingFileSystemEntity<Directory, io.Directory>
+class RecordingDirectory extends RecordingFileSystemEntity<Directory>
     implements Directory {
   /// Creates a new `RecordingDirectory`.
   RecordingDirectory(RecordingFileSystem fileSystem, io.Directory delegate)
@@ -29,7 +28,7 @@
   }
 
   @override
-  Directory wrap(io.Directory delegate) =>
+  Directory wrap(Directory delegate) =>
       super.wrap(delegate) ?? wrapDirectory(delegate);
 
   Future<Directory> _create({bool recursive: false}) =>
diff --git a/lib/src/backends/record_replay/recording_file.dart b/lib/src/backends/record_replay/recording_file.dart
index 4eeeeae..51b447a 100644
--- a/lib/src/backends/record_replay/recording_file.dart
+++ b/lib/src/backends/record_replay/recording_file.dart
@@ -9,17 +9,36 @@
 import 'package:file/file.dart';
 import 'package:file/src/io.dart' as io;
 
-import 'mutable_recording.dart';
 import 'recording_file_system.dart';
 import 'recording_file_system_entity.dart';
 import 'recording_io_sink.dart';
 import 'recording_random_access_file.dart';
 import 'result_reference.dart';
 
+/// Callback responsible for synchronously writing result [data] to the
+/// specified [file].
+///
+/// See also:
+///   - [_BlobReference]
+typedef void _BlobDataSyncWriter<T>(File file, T data);
+
+/// Callback responsible for asynchronously writing result [data] to the
+/// specified [file].
+///
+/// See also:
+///   - [_BlobFutureReference]
+typedef Future<Null> _BlobDataAsyncWriter<T>(File file, T data);
+
+/// Callback responsible writing streaming result [data] to the specified
+/// [sink].
+///
+/// See also:
+///   - [_BlobStreamReference]
+typedef void _BlobDataStreamWriter<T>(IOSink sink, T data);
+
 /// [File] implementation that records all invocation activity to its file
 /// system's recording.
-class RecordingFile extends RecordingFileSystemEntity<File, io.File>
-    implements File {
+class RecordingFile extends RecordingFileSystemEntity<File> implements File {
   /// Creates a new `RecordingFile`.
   RecordingFile(RecordingFileSystem fileSystem, io.File delegate)
       : super(fileSystem, delegate) {
@@ -50,7 +69,9 @@
   }
 
   @override
-  File wrap(io.File delegate) => super.wrap(delegate) ?? wrapFile(delegate);
+  File wrap(File delegate) => super.wrap(delegate) ?? wrapFile(delegate);
+
+  File _newRecordingFile() => recording.newFile(delegate.basename);
 
   RandomAccessFile _wrapRandomAccessFile(RandomAccessFile delegate) =>
       new RecordingRandomAccessFile(fileSystem, delegate);
@@ -68,12 +89,15 @@
   RandomAccessFile _openSync({FileMode mode: FileMode.READ}) =>
       _wrapRandomAccessFile(delegate.openSync(mode: mode));
 
-  StreamReference<List<int>> _openRead([int start, int end]) =>
-      new _ByteArrayStreamReference(
-        recording,
-        'openRead',
-        delegate.openRead(start, end),
-      );
+  StreamReference<List<int>> _openRead([int start, int end]) {
+    return new _BlobStreamReference<List<int>>(
+      file: _newRecordingFile(),
+      stream: delegate.openRead(start, end),
+      writer: (IOSink sink, List<int> bytes) {
+        sink.add(bytes);
+      },
+    );
+  }
 
   IOSink _openWrite({FileMode mode: FileMode.WRITE, Encoding encoding: UTF8}) {
     return new RecordingIOSink(
@@ -82,45 +106,65 @@
     );
   }
 
-  FutureReference<List<int>> _readAsBytes() => new _ByteArrayFutureReference(
-        recording,
-        'readAsBytes',
-        delegate.readAsBytes(),
-      );
+  FutureReference<List<int>> _readAsBytes() {
+    return new _BlobFutureReference<List<int>>(
+      file: _newRecordingFile(),
+      future: delegate.readAsBytes(),
+      writer: (File file, List<int> bytes) async {
+        await file.writeAsBytes(bytes, flush: true);
+      },
+    );
+  }
 
-  ResultReference<List<int>> _readAsBytesSync() => new _ByteArrayReference(
-        recording,
-        'readAsBytesSync',
-        delegate.readAsBytesSync(),
-      );
+  ResultReference<List<int>> _readAsBytesSync() {
+    return new _BlobReference<List<int>>(
+      file: _newRecordingFile(),
+      value: delegate.readAsBytesSync(),
+      writer: (File file, List<int> bytes) {
+        file.writeAsBytesSync(bytes, flush: true);
+      },
+    );
+  }
 
-  FutureReference<String> _readAsString({Encoding encoding: UTF8}) =>
-      new _FileContentFutureReference(
-        recording,
-        'readAsString',
-        delegate.readAsString(encoding: encoding),
-      );
+  FutureReference<String> _readAsString({Encoding encoding: UTF8}) {
+    return new _BlobFutureReference<String>(
+      file: _newRecordingFile(),
+      future: delegate.readAsString(encoding: encoding),
+      writer: (File file, String content) async {
+        await file.writeAsString(content, flush: true);
+      },
+    );
+  }
 
-  ResultReference<String> _readAsStringSync({Encoding encoding: UTF8}) =>
-      new _FileContentReference(
-        recording,
-        'readAsStringSync',
-        delegate.readAsStringSync(encoding: encoding),
-      );
+  ResultReference<String> _readAsStringSync({Encoding encoding: UTF8}) {
+    return new _BlobReference<String>(
+      file: _newRecordingFile(),
+      value: delegate.readAsStringSync(encoding: encoding),
+      writer: (File file, String content) {
+        file.writeAsStringSync(content, flush: true);
+      },
+    );
+  }
 
-  FutureReference<List<String>> _readAsLines({Encoding encoding: UTF8}) =>
-      new _LinesFutureReference(
-        recording,
-        'readAsLines',
-        delegate.readAsLines(encoding: encoding),
-      );
+  FutureReference<List<String>> _readAsLines({Encoding encoding: UTF8}) {
+    return new _BlobFutureReference<List<String>>(
+      file: _newRecordingFile(),
+      future: delegate.readAsLines(encoding: encoding),
+      writer: (File file, List<String> lines) async {
+        await file.writeAsString(lines.join('\n'), flush: true);
+      },
+    );
+  }
 
-  ResultReference<List<String>> _readAsLinesSync({Encoding encoding: UTF8}) =>
-      new _LinesReference(
-        recording,
-        'readAsLinesSync',
-        delegate.readAsLinesSync(encoding: encoding),
-      );
+  ResultReference<List<String>> _readAsLinesSync({Encoding encoding: UTF8}) {
+    return new _BlobReference<List<String>>(
+      file: _newRecordingFile(),
+      value: delegate.readAsLinesSync(encoding: encoding),
+      writer: (File file, List<String> lines) {
+        file.writeAsStringSync(lines.join('\n'), flush: true);
+      },
+    );
+  }
 
   Future<File> _writeAsBytes(
     List<int> bytes, {
@@ -140,19 +184,24 @@
           .then(wrap);
 }
 
-abstract class _ExternalReference<T> extends ResultReference<T> {
-  final File file;
+/// A [ResultReference] that serializes its value data to a separate file.
+class _BlobReference<T> extends ResultReference<T> {
+  final File _file;
   final T _value;
+  final _BlobDataSyncWriter<T> _writer;
 
-  _ExternalReference(MutableRecording recording, String name, this._value)
-      : file = recording.newFile(name);
-
-  @protected
-  void writeDataToFile(T value);
+  _BlobReference({
+    @required File file,
+    @required T value,
+    @required _BlobDataSyncWriter<T> writer,
+  })
+      : _file = file,
+        _value = value,
+        _writer = writer;
 
   @override
   T get value {
-    writeDataToFile(_value);
+    _writer(_file, _value);
     return _value;
   }
 
@@ -160,47 +209,58 @@
   T get recordedValue => _value;
 
   @override
-  dynamic get serializedValue => '!${file.basename}';
+  dynamic get serializedValue => '!${_file.basename}';
 }
 
-abstract class _ExternalFutureReference<T> extends FutureReference<T> {
-  final File file;
+/// A [FutureReference] that serializes its value data to a separate file.
+class _BlobFutureReference<T> extends FutureReference<T> {
+  final File _file;
+  final _BlobDataAsyncWriter<T> _writer;
 
-  _ExternalFutureReference(
-      MutableRecording recording, String name, Future<T> future)
-      : file = recording.newFile(name),
+  _BlobFutureReference({
+    @required File file,
+    @required Future<T> future,
+    @required _BlobDataAsyncWriter<T> writer,
+  })
+      : _file = file,
+        _writer = writer,
         super(future);
 
-  @protected
-  Future<Null> writeDataToFile(T value);
-
   @override
   Future<T> get value {
     return super.value.then((T value) async {
-      await writeDataToFile(value);
+      await _writer(_file, value);
       return value;
     });
   }
 
   @override
-  dynamic get serializedValue => '!${file.basename}';
+  dynamic get serializedValue => '!${_file.basename}';
 }
 
-class _ByteArrayStreamReference extends StreamReference<List<int>> {
-  final File file;
+/// A [StreamReference] that serializes its value data to a separate file.
+class _BlobStreamReference<T> extends StreamReference<T> {
+  final File _file;
+  final _BlobDataStreamWriter<T> _writer;
   IOSink _sink;
 
-  _ByteArrayStreamReference(
-      MutableRecording recording, String name, Stream<List<int>> stream)
-      : file = recording.newFile(name)..createSync(),
-        super(stream);
+  _BlobStreamReference({
+    @required File file,
+    @required Stream<T> stream,
+    @required _BlobDataStreamWriter<T> writer,
+  })
+      : _file = file,
+        _writer = writer,
+        super(stream) {
+    _file.createSync();
+  }
 
   @override
-  void onData(List<int> event) {
+  void onData(T event) {
     if (_sink == null) {
-      _sink = file.openWrite();
+      _sink = _file.openWrite();
     }
-    _sink.add(event);
+    _writer(_sink, event);
   }
 
   @override
@@ -211,73 +271,10 @@
   }
 
   @override
-  dynamic get serializedValue => '!${file.basename}';
+  dynamic get serializedValue => '!${_file.basename}';
 
   // TODO(tvolkert): remove `.then()` once Dart 1.22 is in stable
   @override
   Future<Null> get complete =>
       Future.wait(<Future<dynamic>>[super.complete, _sink.done]).then((_) {});
 }
-
-class _ByteArrayFutureReference extends _ExternalFutureReference<List<int>> {
-  _ByteArrayFutureReference(
-      MutableRecording recording, String name, Future<List<int>> future)
-      : super(recording, name, future);
-
-  @override
-  Future<Null> writeDataToFile(List<int> bytes) async {
-    await file.writeAsBytes(bytes, flush: true);
-  }
-}
-
-class _ByteArrayReference extends _ExternalReference<List<int>> {
-  _ByteArrayReference(MutableRecording recording, String name, List<int> bytes)
-      : super(recording, name, bytes);
-
-  @override
-  void writeDataToFile(List<int> bytes) {
-    file.writeAsBytesSync(bytes, flush: true);
-  }
-}
-
-class _FileContentFutureReference extends _ExternalFutureReference<String> {
-  _FileContentFutureReference(
-      MutableRecording recording, String name, Future<String> future)
-      : super(recording, name, future);
-
-  @override
-  Future<Null> writeDataToFile(String content) async {
-    await file.writeAsString(content, flush: true);
-  }
-}
-
-class _FileContentReference extends _ExternalReference<String> {
-  _FileContentReference(MutableRecording recording, String name, String content)
-      : super(recording, name, content);
-
-  @override
-  void writeDataToFile(String content) {
-    file.writeAsStringSync(content, flush: true);
-  }
-}
-
-class _LinesFutureReference extends _ExternalFutureReference<List<String>> {
-  _LinesFutureReference(
-      MutableRecording recording, String name, Future<List<String>> future)
-      : super(recording, name, future);
-
-  @override
-  Future<Null> writeDataToFile(List<String> lines) async {
-    await file.writeAsString(lines.join('\n'), flush: true);
-  }
-}
-
-class _LinesReference extends _ExternalReference<List<String>> {
-  _LinesReference(MutableRecording recording, String name, List<String> lines)
-      : super(recording, name, lines);
-
-  @override
-  void writeDataToFile(List<String> lines) {
-    file.writeAsStringSync(lines.join('\n'), flush: true);
-  }
-}
diff --git a/lib/src/backends/record_replay/recording_file_system_entity.dart b/lib/src/backends/record_replay/recording_file_system_entity.dart
index e1f695b..91cb515 100644
--- a/lib/src/backends/record_replay/recording_file_system_entity.dart
+++ b/lib/src/backends/record_replay/recording_file_system_entity.dart
@@ -18,10 +18,8 @@
 
 /// [FileSystemEntity] implementation that records all invocation activity to
 /// its file system's recording.
-abstract class RecordingFileSystemEntity<T extends FileSystemEntity,
-        D extends io.FileSystemEntity> extends Object
-    with RecordingProxyMixin
-    implements FileSystemEntity {
+abstract class RecordingFileSystemEntity<T extends FileSystemEntity>
+    extends Object with RecordingProxyMixin implements FileSystemEntity {
   /// Creates a new `RecordingFileSystemEntity`.
   RecordingFileSystemEntity(this.fileSystem, this.delegate) {
     methods.addAll(<Symbol, Function>{
@@ -44,6 +42,8 @@
       #isAbsolute: () => delegate.isAbsolute,
       #absolute: _getAbsolute,
       #parent: _getParent,
+      #basename: () => delegate.basename,
+      #dirname: () => delegate.dirname,
     });
   }
 
@@ -62,7 +62,7 @@
   /// The entity to which this entity delegates its functionality while
   /// recording.
   @protected
-  final D delegate;
+  final T delegate;
 
   /// Returns an entity with the same file system and same type as this
   /// entity but backed by the specified delegate.
@@ -74,7 +74,7 @@
   /// returns `null`.
   @protected
   @mustCallSuper
-  T wrap(D delegate) => delegate == this.delegate ? this as T : null;
+  T wrap(T delegate) => delegate == this.delegate ? this as T : null;
 
   /// Returns a directory with the same file system as this entity but backed
   /// by the specified delegate directory.
@@ -94,15 +94,15 @@
 
   Future<T> _rename(String newPath) => delegate
       .rename(newPath)
-      .then((io.FileSystemEntity entity) => wrap(entity as D));
+      .then((io.FileSystemEntity entity) => wrap(entity as T));
 
-  T _renameSync(String newPath) => wrap(delegate.renameSync(newPath) as D);
+  T _renameSync(String newPath) => wrap(delegate.renameSync(newPath) as T);
 
   Future<T> _delete({bool recursive: false}) => delegate
       .delete(recursive: recursive)
-      .then((io.FileSystemEntity entity) => wrap(entity as D));
+      .then((io.FileSystemEntity entity) => wrap(entity as T));
 
-  T _getAbsolute() => wrap(delegate.absolute as D);
+  T _getAbsolute() => wrap(delegate.absolute as T);
 
   Directory _getParent() => wrapDirectory(delegate.parent);
 }
diff --git a/lib/src/backends/record_replay/recording_link.dart b/lib/src/backends/record_replay/recording_link.dart
index 0114499..e06612e 100644
--- a/lib/src/backends/record_replay/recording_link.dart
+++ b/lib/src/backends/record_replay/recording_link.dart
@@ -12,8 +12,7 @@
 
 /// [Link] implementation that records all invocation activity to its file
 /// system's recording.
-class RecordingLink extends RecordingFileSystemEntity<Link, io.Link>
-    implements Link {
+class RecordingLink extends RecordingFileSystemEntity<Link> implements Link {
   /// Creates a new `RecordingLink`.
   RecordingLink(RecordingFileSystem fileSystem, io.Link delegate)
       : super(fileSystem, delegate) {
@@ -28,7 +27,7 @@
   }
 
   @override
-  Link wrap(io.Link delegate) => super.wrap(delegate) ?? wrapLink(delegate);
+  Link wrap(Link delegate) => super.wrap(delegate) ?? wrapLink(delegate);
 
   Future<Link> _create(String target, {bool recursive: false}) =>
       delegate.create(target, recursive: recursive).then(wrap);
diff --git a/test/recording_test.dart b/test/recording_test.dart
index 18a273d..9ed12c7 100644
--- a/test/recording_test.dart
+++ b/test/recording_test.dart
@@ -491,7 +491,7 @@
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
                 containsPair('namedArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.openRead$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
               ));
           File file = _getRecordingFile(recording, manifest[1]['result']);
           expect(file, exists);
@@ -521,7 +521,7 @@
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
                 containsPair('namedArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.readAsBytes$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
               ));
           File file = _getRecordingFile(recording, manifest[1]['result']);
           expect(file, exists);
@@ -549,7 +549,7 @@
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
                 containsPair('namedArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.readAsBytesSync$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
               ));
           File file = _getRecordingFile(recording, manifest[1]['result']);
           expect(file, exists);
@@ -578,7 +578,7 @@
                 containsPair('method', 'readAsString'),
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.readAsString$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
                 containsPair(
                     'namedArguments',
                     allOf(
@@ -613,7 +613,7 @@
                 containsPair('method', 'readAsStringSync'),
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.readAsStringSync$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
                 containsPair(
                     'namedArguments',
                     allOf(
@@ -646,7 +646,7 @@
                 containsPair('method', 'readAsLines'),
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.readAsLines$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
                 containsPair('namedArguments', isEmpty),
               ));
           File file = _getRecordingFile(recording, manifest[1]['result']);
@@ -674,7 +674,7 @@
                 containsPair('method', 'readAsLinesSync'),
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
-                containsPair('result', matches(r'^![0-9]+.readAsLinesSync$')),
+                containsPair('result', matches(r'^![0-9]+.foo$')),
                 containsPair('namedArguments', isEmpty),
               ));
           File file = _getRecordingFile(recording, manifest[1]['result']);