public enum Platform
{
MacOS, Windows, Linux,
}
public enum OutputFormat
{
AAC, Opus
}
2.定義了一個 EncoderFrontendAttribute 類別。這個類別繼承自 Attribute 類別,代表它是一個自訂屬性。
這個類別表示一個轉碼前端,提供了一個建構函式來指定它的名稱、輸出格式、以及支援的平台。
public class EncoderFrontendAttribute : Attribute
{
public string Name;
public OutputFormat OutputFormat;
public Platform[] SupportedPlatforms;
public EncoderFrontendAttribute(string name, OutputFormat outputFormat, params Platform[] supportedPlatforms)
{
Name = name;
OutputFormat = outputFormat;
SupportedPlatforms = supportedPlatforms;
}
}
public interface IEncoder
{
public string ExecutableName { get; }
public bool WritesOutputToStdErr { get; }
bool EnsureEncoderExists();
IArgumentBuilder Configure();
virtual TranscodingJob ConfigureTranscodingJob(TranscodingJobRequest request);
}
5.定義了一個 Qaac 類別,該類別提供了轉碼器的執行檔名稱、檢查轉碼器是否存在、以及設定轉碼器的參數的功能。
使用自訂屬性 EncoderFrontend 來標註這個類別。這個類別實作了 IEncoder 介面,表示它是一個轉碼器。
[EncoderFrontend(nameof(Qaac), OutputFormat.AAC, Platform.Windows)]
public class Qaac : IEncoder
{
public string ExecutableName => "qaac";
public bool WritesOutputToStdErr => true;
public bool EnsureEncoderExists()
{
return CommonEncoderMethods.CheckEncoderExists(ExecutableName);
}
public IArgumentBuilder Configure()
{
return new QaacBuilder();
}
}
總結一下,到目前為止,您已經建立了一個轉碼器必須實作的共同介面,以及一個自訂屬性,用於描述提供適當轉碼器所需的資料。
6.建立工廠
定義了一個名為 IEncoderFactory 的介面,以及一個名為 EncoderFactory 的類別,並且讓這個類別實作了 IEncoderFactory 介面。
這個工廠提供了兩個功能:獲取當前運行的平台,以及獲取指定格式的轉碼器。
public interface IEncoderFactory
{
public Platform GetPlatform();
public IEncoder? GetEncoder(OutputFormat format);
}
public class EncoderFactory : IEncoderFactory
{
// it must be overrideable so it can be mocked
public virtual Platform GetPlatform()
{
if (OperatingSystem.IsMacOS())
{
return Platform.MacOS;
}
if (OperatingSystem.IsLinux())
{
return Platform.Linux;
}
if (OperatingSystem.IsWindows())
{
return Platform.Windows;
}
throw new PlatformNotSupportedException("Only Windows, Linux and macOS are currently supported platforms.");
}
public IEncoder? GetEncoder(OutputFormat format)
{
var assemblies = typeof(IEncoder).Assembly;
var encoders = assemblies
.GetTypes()
// get IEncoder classes
.Where(x => x.GetInterface(nameof(IEncoder)) != null);
foreach (var type in encoders)
{
var attribute = (EncoderFrontendAttribute)Attribute
.GetCustomAttribute(type, typeof(EncoderFrontendAttribute))!;
if (attribute.OutputFormat == format && attribute.SupportedPlatforms.Any(p => p == GetPlatform()))
{
return Activator.CreateInstance(type) as IEncoder;
}
}
return null;
}
}
這個工廠先使用反射獲取所有實作了 IEncoder 介面的類別,然後對這些類別迭代,並使用反射獲取它們附加的自訂屬性。最後,如果自訂屬性符合我們期望的輸出格式,並在當前的平台上運行,就返回一個轉碼器的新實例。
使用這個工廠的簡單寫法:
var encoder = _encoderFactory.GetEncoder(OutputFormat.AAC);
8.測試這個工廠
為了確保工廠的正常運行,我們使用模擬庫 NSubstitute 來寫測試,讓您在不修改類別功能的前提下模擬類別的一部分。
我們寫兩個測試方法:
這些測試方法可以確保在不同的平台上,能夠獲取預期的轉碼器。
public class EncoderFactoryTests
{
private readonly IEncoderFactory _encoderFactory;
public EncoderFactoryTests()
{
_encoderFactory = Substitute.ForPartsOf<EncoderFactory>();
}
[Fact]
public void GetEncoder_AACOnMacOS_ReturnsFFMPEG()
{
// arrange
_encoderFactory.Configure().GetPlatform().Returns(Platform.MacOS);
// act
var encoder = _encoderFactory.GetEncoder(OutputFormat.AAC);
// assert
Assert.NotNull(encoder);
var encoderType = encoder.GetType();
Assert.Equal(nameof(FfmpegForMacOS), encoderType.Name);
}
[Fact]
public void GetEncoder_AACOnWindows_ReturnsQaac()
{
// arrange
_encoderFactory.Configure().GetPlatform().Returns(Platform.Windows);
// act
var encoder = _encoderFactory.GetEncoder(OutputFormat.AAC);
// assert
Assert.NotNull(encoder);
var encoderType = encoder.GetType()!;
Assert.Equal(nameof(Qaac), encoderType.Name);
}
}
使用 NSubstitute 模擬了 EncoderFactory 類別的 GetPlatform 方法,以確保它返回預期的平台。接著,它們呼叫 GetEncoder 方法並斷言返回的轉碼器的類型是預期的類型。