Goodbye to the cross-platform bridge

by Finnian Reilly (modified: 2016 Jun 27)

Introduction

Lately I have had a change of mind about using the bridge pattern to make cross-platform classes, like those used in the Vision2 GUI library for example. I now favor using either the object factory pattern, or the simpler explict creation type construct as for example:

local object: MY_CLASS_I do create {MY_CLASS_IMP} object.make end
The _I and _IMP suffixes denote respectively, a deferred interface and current platform implementation.

The Cross-platform Bridge

This pattern can be expressed abstractly by the following two classes:

class EL_CROSS_PLATFORM [I -> EL_PLATFORM_IMPLEMENTATION create default_create end] feature {NONE} -- Initialization make_default -- do create implementation implementation.set_interface (Current) implementation.make end feature {NONE} -- Implementation implementation: I end

class EL_PLATFORM_IMPLEMENTATION feature {EL_CROSS_PLATFORM} -- Initialization make require interface_set: attached interface -- This implies the instance must be created first with `default_create' -- and then `set_interface' called. See class `EL_CROSS_PLATFORM' do end feature -- Element change set_interface (a_interface: like interface) do interface := a_interface end feature {EL_CROSS_PLATFORM} -- Implementation interface: ANY end

The main advantage of this pattern is that it hides from the developer-user the fact that the class relies on different implementations for each development platform. However my experience of using this pattern over many years, is that it leads to unweildly code that is relatively difficult to maintain and extend. Using a deferred interface parent class with mutiple cross-platform direct descendants, genarally makes for cleaner code that is easier to maintain and extend.

Object Factory Example

Here is an example of an object factory class that is shared from the class EL_MODULE_AUDIO_COMMAND:

A Command Factory

class EL_AUDIO_COMMAND_FACTORY feature -- Factory new_wave_generation (output_file_path: EL_FILE_PATH): EL_WAV_GENERATION_COMMAND_I do create {EL_WAV_GENERATION_COMMAND_IMP} Result.make (output_file_path) -- make calls execute end new_wav_to_mp3 (input_file_path, output_file_path: EL_FILE_PATH): EL_WAV_TO_MP3_COMMAND_I do create {EL_WAV_TO_MP3_COMMAND_IMP} Result.make (input_file_path, output_file_path) end new_extract_mp3_info (file_path: EL_FILE_PATH): EL_EXTRACT_MP3_INFO_COMMAND_I do create {EL_EXTRACT_MP3_INFO_COMMAND_IMP} Result.make (file_path) end new_audio_properties (file_path: EL_FILE_PATH): EL_AUDIO_PROPERTIES_COMMAND_I do create {EL_AUDIO_PROPERTIES_COMMAND_IMP} Result.make (file_path) end new_video_to_mp3 (input_file_path, output_file_path: EL_FILE_PATH): EL_VIDEO_TO_MP3_COMMAND_I do create {EL_VIDEO_TO_MP3_COMMAND_IMP} Result.make (input_file_path, output_file_path) end new_mp3_to_wav_clip_saver (input_file_path, output_file_path: EL_FILE_PATH): EL_MP3_TO_WAV_CLIP_SAVER_COMMAND_I do create {EL_MP3_TO_WAV_CLIP_SAVER_COMMAND_IMP} Result.make (input_file_path, output_file_path) end new_wav_fader (input_file_path, output_file_path: EL_FILE_PATH): EL_WAV_FADER_I do create {EL_WAV_FADER_IMP} Result.make (input_file_path, output_file_path) end end

Using the Command Factory

feature -- Basic operations write_clip (a_offset_secs: INTEGER; a_fade_in_duration, a_fade_out_duration: REAL) -- Write clip from full length song at offset with fade in and fade out local clip_saver: like Audio_command.new_mp3_to_wav_clip_saver convertor: like Audio_command.new_wav_to_mp3; fader: like Audio_command.new_wav_fader wav_path, faded_wav_path: EL_FILE_PATH audio_properties: like Audio_command.new_audio_properties do wav_path := mp3_path.with_new_extension ("wav") faded_wav_path := mp3_path.with_new_extension ("faded.wav") -- Cutting clip_saver := Audio_command.new_mp3_to_wav_clip_saver (source_song.mp3_path, wav_path) clip_saver.set_duration (duration); clip_saver.set_offset (a_offset_secs) File_system.make_directory (mp3_path.parent) clip_saver.execute -- Fading fader := Audio_command.new_wav_fader (wav_path, faded_wav_path) fader.set_duration (duration) fader.set_fade_in (a_fade_in_duration) fader.set_fade_out (a_fade_out_duration) fader.execute File_system.delete_file (wav_path) -- Compressing convertor := Audio_command.new_wav_to_mp3 (faded_wav_path, mp3_path) audio_properties := Audio_command.new_audio_properties (mp3_path) convertor.set_bit_rate_per_channel (audio_properties.bit_rate) convertor.execute File_system.delete_file (faded_wav_path) write_id3_info (id3_info) end

Typical Platform Implemenation

Note that all features of the interface are exported to NONE. This enforces the intent that the class be only useable through the interface. note description: "[ Implementation of `EL_VIDEO_TO_MP3_COMMAND_I' interface Does AAC -> MP3 audio conversion ]" class EL_VIDEO_TO_MP3_COMMAND_IMP inherit EL_VIDEO_TO_MP3_COMMAND_I export {NONE} all end EL_OS_COMMAND_IMP undefine make_default end create make feature {NONE} -- Constants Template: STRING = "[ $command_name -v quiet -i $input_file_path #if $has_offset_time then -ss $offset_time #end #if $has_duration then -t ${duration} #end -ab ${bit_rate}k -id3v2_version 4 $output_file_path ]" end

A Code Comparison on Github

Lately I have eliminated all uses of the CP bridge pattern from the Eiffel-Loop library. For posterity I preserved some of the obsolete classes used for wrapping OS commands in a separate directory. You can see the differences in the implemenation on github here:

The new implemenation has an improved inheritance heirarchy. This is the EL_OS_COMMAND_I heirarchy found in the example program manage-mp3:EL_OS_COMMAND_I* EL_SINGLE_PATH_OPERAND_COMMAND_I* EL_AUDIO_PROPERTIES_COMMAND_I* EL_AUDIO_PROPERTIES_COMMAND_IMP EL_DELETION_COMMAND_I* EL_DELETE_TREE_COMMAND_I* EL_DELETE_TREE_COMMAND_IMP EL_DELETE_FILE_COMMAND_I* EL_DELETE_FILE_COMMAND_IMP EL_MAKE_DIRECTORY_COMMAND_I* EL_MAKE_DIRECTORY_COMMAND_IMP EL_FIND_COMMAND_I* EL_FIND_FILES_COMMAND_I* EL_FIND_FILES_COMMAND_IMP EL_FIND_DIRECTORIES_COMMAND_I* EL_FIND_DIRECTORIES_COMMAND_IMP EL_USER_LIST_COMMAND_I* EL_USER_LIST_COMMAND_IMP EL_DIRECTORY_INFO_COMMAND_I* EL_DIRECTORY_INFO_COMMAND_IMP EL_DOUBLE_PATH_OPERAND_COMMAND_I* EL_FILE_RELOCATION_COMMAND_I* EL_COPY_TREE_COMMAND_I* EL_COPY_TREE_COMMAND_IMP EL_COPY_FILE_COMMAND_I* EL_COPY_FILE_COMMAND_IMP EL_MOVE_FILE_COMMAND_I* EL_MOVE_FILE_COMMAND_IMP EL_FILE_CONVERSION_COMMAND_I* EL_WAV_TO_MP3_COMMAND_I* EL_WAV_TO_MP3_COMMAND_IMP EL_VIDEO_TO_MP3_COMMAND_I* EL_VIDEO_TO_MP3_COMMAND_IMP EL_MP3_TO_WAV_CLIP_SAVER_COMMAND_I* EL_MP3_TO_WAV_CLIP_SAVER_COMMAND_IMP EL_WAV_FADER_I* EL_WAV_FADER_IMP EL_EXTRACT_MP3_INFO_COMMAND_I* EL_EXTRACT_MP3_INFO_COMMAND_IMP EL_WAV_GENERATION_COMMAND_I* EL_WAV_GENERATION_COMMAND_IMP EL_CAPTURED_OS_COMMAND_I* EL_FIND_COMMAND_I*... EL_USER_LIST_COMMAND_I*... EL_CPU_INFO_COMMAND_I* EL_CPU_INFO_COMMAND_IMP EL_DIRECTORY_INFO_COMMAND_I*... EL_EXTRACT_MP3_INFO_COMMAND_I*... EL_GVFS_OS_COMMAND EL_GVFS_FILE_LIST_COMMAND EL_GVFS_FILE_COUNT_COMMAND EL_GVFS_FILE_EXISTS_COMMAND EL_GVFS_MOUNT_LIST_COMMAND EL_GVFS_REMOVE_FILE_COMMAND EL_IP_ADAPTER_INFO_COMMAND_I* EL_IP_ADAPTER_INFO_COMMAND_IMP EL_AVCONV_OS_COMMAND_I* EL_AUDIO_PROPERTIES_COMMAND_I*... EL_VIDEO_TO_MP3_COMMAND_I*... EL_MP3_TO_WAV_CLIP_SAVER_COMMAND_I*... EL_OS_COMMAND EL_GVFS_FILE_LIST_COMMAND EL_GVFS_FILE_COUNT_COMMAND EL_GVFS_FILE_EXISTS_COMMAND EL_GVFS_MOUNT_LIST_COMMAND EL_GVFS_REMOVE_FILE_COMMAND DETECT_RHYTHMBOX_COMMAND EL_GVFS_OS_COMMAND... EL_OS_COMMAND_IMP* EL_FIND_FILES_COMMAND_IMP EL_COPY_FILE_COMMAND_IMP EL_COPY_TREE_COMMAND_IMP EL_MOVE_FILE_COMMAND_IMP EL_DELETE_TREE_COMMAND_IMP EL_FIND_DIRECTORIES_COMMAND_IMP EL_MAKE_DIRECTORY_COMMAND_IMP EL_CPU_INFO_COMMAND_IMP EL_USER_LIST_COMMAND_IMP EL_DIRECTORY_INFO_COMMAND_IMP EL_DELETE_FILE_COMMAND_IMP EL_IP_ADAPTER_INFO_COMMAND_IMP EL_EXTRACT_MP3_INFO_COMMAND_IMP EL_WAV_TO_MP3_COMMAND_IMP EL_WAV_GENERATION_COMMAND_IMP EL_AUDIO_PROPERTIES_COMMAND_IMP EL_VIDEO_TO_MP3_COMMAND_IMP EL_MP3_TO_WAV_CLIP_SAVER_COMMAND_IMP EL_WAV_FADER_IMP EL_OS_COMMAND...