我目前正在开发一个系统,它可以接受不同类型的文件,有些类只对特定类型的文件感兴趣,而有些则根本不区分。
我决定采用一种基于事件的方法,因为可能没有使用者,而且可能有很多,我真的不想在通知器类中引入任何依赖项,也不想用不必要的逻辑干扰它们。
为了简化通知器的工作,我编写了一个助手类- ManagerHelper,这避免了在每个类中过滤输入的需要,并且在系统中添加了新的文件类型时也提供了扩展性,否则系统中会需要对每个通知程序类进行更改。
这是当前这些类的工作原理图:

足够简单的结构,它就能完成任务。
所有文件类型都继承具有文件可能具有的最基本信息的公共接口:
public interface IFileInformation
{
string FileName { get; }
FileInfo FileInfo { get; }
Uri Uri { get; }
}接口的基本实现是FileInformation,它主要用于文本文档等文件:
public class FileInformation : IFileInformation
{
public string FileName { get; }
public FileInfo FileInfo { get; }
public Uri Uri { get; }
public FileInformation(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentNullException(nameof(filePath));
}
FileInfo = new FileInfo(filePath);
FileName = Path.GetFileNameWithoutExtension(FileInfo.Name);
Uri = new Uri(FileInfo.FullName);
}
public FileInformation(Uri fileUri)
: this(fileUri.OriginalString)
{
}
}对于媒体文件,我们有MediaFileInformation,它有一个TimeSpan FileLength属性,还有一个属性+ DependencyProperty,用于文件播放/不播放的状态:
public class MediaFileInformation : DependencyObject, IFileInformation, INotifyPropertyChanged
{
public TimeSpan FileLength { get; }
public string FileName { get; }
public FileInfo FileInfo { get; }
public Uri Uri { get; }
public static readonly DependencyProperty IsPlayingProperty =
DependencyProperty.Register(nameof(IsPlaying), typeof(bool), typeof(MediaFileInformation),
new PropertyMetadata(null));
public bool IsPlaying
{
get => (bool)GetValue(IsPlayingProperty);
set
{
SetValue(IsPlayingProperty, value);
OnPropertyChanged();
}
}
public MediaFileInformation(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentNullException(nameof(filePath));
}
FileInfo = new FileInfo(filePath);
FileName = Path.GetFileNameWithoutExtension(FileInfo.Name);
FileLength = FileInfo.GetFileDuration();
Uri = new Uri(FileInfo.FullName);
}
public MediaFileInformation(Uri fileUri)
: this(fileUri.OriginalString)
{
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}实际上只有一个通用的静态类-- Manager<TFileInformation>。它包含一个单一事件,以及很少有相同方法的重载来请求新的文件更新:
public static class Manager<TFileInformation>
where TFileInformation : IFileInformation
{
public static event EventHandler<ManagerEventArgs<TFileInformation>> NewRequest;
public static void Request(IEnumerable<TFileInformation> selectedFiles)
{
Request(new ManagerEventArgs<TFileInformation>(selectedFiles));
}
public static void Request(ManagerEventArgs<TFileInformation> args)
{
OnNewRequest(args);
}
private static void Request(IEnumerable<object> selectedFiles)
{
Request(selectedFiles.Cast<TFileInformation>());
}
private static void OnNewRequest(ManagerEventArgs<TFileInformation> args)
{
NewRequest?.Invoke(typeof(Manager<TFileInformation>), args);
}
}如图所示,该类服务器作为中介,通知每个Manager以及适当的信息,它们必须调用各自的事件。如果您已经注意到,在private类中有一个奇怪的Manager方法,它在这里使用,因为我试图将IEnumerable<object>的泛型类型参数转换为IFileInformation的特定实现时遇到了一些问题。
我想出的唯一解决方案,即保持Manager类的类型安全性,是创建一个私有方法,并仅通过反射访问该方法,并允许Manager类将IEnumerable<object>转换到适当的IEnumerable<TFileInformation>。
public static class ManagerHelper
{
private static readonly Dictionary<Type, Action<IEnumerable<object>>> _newRequests;
static ManagerHelper()
{
var fileInformations = typeof(IFileInformation).GetDerivedTypesFor(Assembly.GetExecutingAssembly());
_newRequests = new Dictionary<Type, Action<IEnumerable<object>>>();
foreach (var information in fileInformations)
{
var instance = typeof(Manager<>).MakeGenericType(information);
var methodInfo = instance.GetMethod("Request", BindingFlags.NonPublic | BindingFlags.Static);
_newRequests.Add(information, enumerable => methodInfo.Invoke(instance, new object[] { enumerable }));
}
}
public static void Request(IEnumerable<IFileInformation> selectedFiles)
{
Request(new ManagerEventArgs<IFileInformation>(selectedFiles));
}
public static void Request(ManagerEventArgs<IFileInformation> args)
{
var typeGroups = args.SelectedFiles.GroupBy(information => information.GetType());
foreach (var typeGroup in typeGroups)
{
_newRequests[typeGroup.Key].Invoke(typeGroup);
}
}
}public class ManagerEventArgs<TFileInformation>
where TFileInformation : IFileInformation
{
public IEnumerable<TFileInformation> SelectedFiles { get; }
public ManagerEventArgs(IEnumerable<TFileInformation> selectedFiles)
{
SelectedFiles = selectedFiles ?? throw new ArgumentNullException(nameof(selectedFiles));
}
}现在,如果您希望在添加X类型的新文件时收到通知,只需执行以下操作:
Manager<X>.NewRequest+=...为通知程序发送不同类型文件的集合也非常简单:
ManagerHelper.Request(files)如果您只想发送筛选过的一堆文件,您也可以:
Manager<X>.Request(files.OfType<X>());你有什么想法?有什么瑕疵吗?有什么我能改进的吗?也许你有更好的替代方案?
我个人认为可以改进命名,也许有一种更好的方法来访问Manager的公共方法,而不是仅仅为了这个用途而使用私有方法。
如果你需要任何额外的信息,请随时在评论中问我。
发布于 2018-03-11 16:09:40
FileInformation和MediaFileInformation中的重复代码
MediaFileInformation应该继承FileInformation以消除重复的代码。在ctor中只需调用base
public class FileInformation
{
public string FileName { get; }
public FileInfo FileInfo { get; }
public Uri Uri { get; }
public FileInformation(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentNullException(nameof(filePath));
}
FileInfo = new FileInfo(filePath);
FileName = Path.GetFileNameWithoutExtension(FileInfo.Name);
Uri = new Uri(FileInfo.FullName);
}
public FileInformation(Uri fileUri)
: this(fileUri.OriginalString)
{
}
}
public class MediaFileInformation : FileInformation
{
public TimeSpan FileLength { get; }
public MediaFileInformation(string filePath)
: base(filePath)
{
FileLength = FileInfo.GetFileDuration();
}
public MediaFileInformation(Uri fileUri)
: base(fileUri)
{
}
}https://codereview.stackexchange.com/questions/189347
复制相似问题