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...