【问题标题】:How to deal with hard to express requirements for dependencies?如何处理难以表达的依赖要求?
【发布时间】:2015-09-01 20:33:03
【问题描述】:

在进行 IoC 时,我(认为我)了解它用于通过组合正确的部分来获得所需的应用程序级功能,以及可测试性的好处。但是在微观层面上,我不太明白如何确保一个对象得到注入它实际上可以使用的依赖项。我的示例是数据库的 BackupMaker

要进行备份,数据库需要以特定格式导出,使用特定压缩算法进行压缩,然后与一些元数据打包在一起形成最终的二进制文件。完成所有这些任务似乎远非一项职责,所以我最终找到了两个合作者:DatabaseExporterCompressor

BackupMaker 并不真正关心数据库是如何导出的(例如,将 IPC 用于数据库软件附带的实用程序,或者通过执行正确的 API 调用),但它确实非常关心结果,即它首先需要是这种数据库备份,采用可传输(与版本无关)格式,我真的不知道如何包装合同。它也不关心压缩器是在内存中还是在磁盘上进行压缩,但它必须是 BZip2。

如果我给BackupMaker 指定了错误类型的导出器或压缩器,它仍然会产生结果,但会损坏——它看起来像一个备份,但它没有应有的格式有。感觉系统的任何其他部分都不能信任给它那些协作者,因为BackupMaker 将无法保证自己做正确的事情;它的工作(从我的角度来看)是生成有效的备份,如果情况不正确,它就不会,更糟糕的是,它不会知道它。同时,即使在写这篇文章的时候,我也觉得我现在在说一些愚蠢的事情,因为单一职责的全部意义在于每个部分都应该做好自己的工作,而不是担心其他人的工作。如果它是那么简单,就不需要合同 - J.B. Rainsberger 刚刚教我有。 (仅供参考,我直接向他发送了这个问题,但我还没有得到回复,关于此事的更多意见会很棒。)

直观地说,我最喜欢的选项是不可能以无效的方式组合类/对象,但我不知道该怎么做。我是否应该编写非常具体的接口名称,例如IDatabaseExportInSuchAndSuchFormatProducerICompressorUsingAlgorithmXAndParametersY,并假设没有类实现这些,如果它们的行为不一样,然后就因为对完全撒谎的代码无能为力而收工吗?我是否应该深入剖析数据库导出和压缩算法的二进制格式的日常任务,以进行合同测试以验证不仅语法而且验证行为,然后确保(但如何?)只使用经过测试的类?或者我可以以某种方式重新分配责任以使这个问题消失吗?是否应该有另一个类负责组合正确的较低级别的元素?还是我分解太多了?

改写

我注意到这个非常特殊的例子得到了很多关注。然而,我的问题比这更笼统。因此,对于赏金的最后一天,我将尝试总结如下。

当使用依赖注入时,根据定义,一个对象依赖于其他对象来满足它的需要。在许多书籍示例中,表示兼容性的方法——提供这种需求的能力——是通过使用类型系统(例如实现一个接口)。除此之外,特别是在动态语言中,使用契约测试。编译器(如果存在)检查语法,合约测试(程序员需要记住的)验证语义。到现在为止还挺好。但是,有时语义仍然过于简单,无法确保某些类/对象可用作另一个类/对象的依赖项,或者过于复杂而无法在合同中正确描述。

在我的示例中,我的依赖于数据库导出器的类认为任何实现 IDatabaseExportInSuchAndSuchFormatProducer 并返回字节为有效的东西(因为我不知道如何验证格式)。 是非常具体的命名和如此粗略的合同,还是我可以做得更好?我应该把合同测试变成集成测试吗?也许(集成)测试所有三个的组成?我并不是真的想成为通用的,但 am 试图保持职责分离并保持可测试性。

【问题讨论】:

  • BackupMaker 的职责是什么。只是编排对DBExporterCompressor 的方法调用吗?此外,在我看来,Compressor 抽象对于您的应用程序来说太笼统了。您想要插入的只是输入/输出流。你真的想要一个带有可注入 I/O 流的 BZip2Compressor 对象。总之,我认为可注入对象不在“BackupMaker”级别,而是在它正在使用的对象中(多个配置更有意义)。

标签: oop dependency-injection language-agnostic inversion-of-control


【解决方案1】:

您在问题中发现的是,您有 2 个类彼此之间存在隐式依赖。所以,最实用的解决方案是让依赖显式

有很多方法可以做到这一点。

选项 1

最简单的选择是让一个服务依赖于另一个服务,并在其抽象中明确依赖服务。


优点

  1. 实现和维护的类型很少。
  2. 只需将压缩服务排除在构造函数之外,就可以跳过特定实现的压缩服务。
  3. DI 容器负责生命周期管理。

缺点

  1. 可能会将不自然的依赖关系强制转换为真正不需要的类型。

public class MySqlExporter : IExporter
{
    private readonly IBZip2Compressor compressor;

    public MySqlExporter(IBZip2Compressor compressor)
    {
        this.compressor = compressor;
    }

    public void Export(byte[] data)
    {
        byte[] compressedData = this.compressor.Compress(data);

        // Export implementation
    }
}

选项 2

由于您要进行不直接依赖于特定压缩算法或数据库的可扩展设计,您可以使用Aggregate Service(实现Facade Pattern)从您的@中抽象出更具体的配置987654326@.

正如文章中所指出的,您有一个隐式域概念(依赖关系的协调),需要将其实现为显式服务,IBackupCoordinator


优点

  1. DI 容器负责生命周期管理。
  2. 将压缩排除在特定实现之外就像通过方法传递数据一样简单。
  3. 显式实现您缺少的域概念,即依赖关系的协调。

缺点

  1. 要构建和维护的类型很多。
  2. BackupManager 必须有 3 个依赖项,而不是向 DI 容器注册的 2 个。

通用接口

public interface IBackupCoordinator
{
    void Export(byte[] data);
    byte[] Compress(byte[] data);
}

public interface IBackupMaker
{
    void Backup();
}

public interface IDatabaseExporter
{
    void Export(byte[] data);
}

public interface ICompressor
{
    byte[] Compress(byte[] data);
}

专用接口

现在,为了确保各部分仅以一种方式组合在一起,您需要制作特定于所使用算法和数据库的接口。您可以使用接口继承来实现这一点(如图所示),或者您可以将接口差异隐藏在外观后面(IBackupCoordinator)。

public interface IBZip2Compressor : ICompressor
{}

public interface IGZipCompressor : ICompressor
{}

public interface IMySqlDatabaseExporter : IDatabaseExporter
{}

public interface ISqlServerDatabaseExporter : IDatabaseExporter
{}

协调器实现

协调员是为您完成这项工作的人。实现之间的细微差别在于接口依赖项被显式调用,因此您不能在 DI 配置中注入错误的类型。

public class BZip2ToMySqlBackupCoordinator : IBackupCoordinator
{
    private readonly IMySqlDatabaseExporter exporter;
    private readonly IBZip2Compressor compressor;

    public BZip2ToMySqlBackupCoordinator(
        IMySqlDatabaseExporter exporter, 
        IBZip2Compressor compressor)
    {
        this.exporter = exporter;
        this.compressor = compressor;
    }

    public void Export(byte[] data)
    {
        this.exporter.Export(byte[] data);
    }

    public byte[] Compress(byte[] data)
    {
        return this.compressor.Compress(data);
    }
}

public class GZipToSqlServerBackupCoordinator : IBackupCoordinator
{
    private readonly ISqlServerDatabaseExporter exporter;
    private readonly IGZipCompressor compressor;

    public BZip2ToMySqlBackupCoordinator(
        ISqlServerDatabaseExporter exporter, 
        IGZipCompressor compressor)
    {
        this.exporter = exporter;
        this.compressor = compressor;
    }

    public void Export(byte[] data)
    {
        this.exporter.Export(byte[] data);
    }

    public byte[] Compress(byte[] data)
    {
        return this.compressor.Compress(data);
    }
}

BackupMaker 实施

BackupMaker 现在可以是通用的,因为它接受任何类型的 IBackupCoordinator 来完成繁重的工作。

public class BackupMaker : IBackupMaker
{
    private readonly IBackupCoordinator backupCoordinator;

    public BackupMaker(IBackupCoordinator backupCoordinator)
    {
        this.backupCoordinator = backupCoordinator;
    }

    public void Backup()
    {
        // Get the data from somewhere
        byte[] data = new byte[0];

        // Compress the data
        byte[] compressedData = this.backupCoordinator.Compress(data);

        // Backup the data
        this.backupCoordinator.Export(compressedData);

    }
}

请注意,即使您的服务在BackupMaker 以外的其他地方使用,这也会将它们整齐地包装到一个可以传递给其他服务的包中。您不必仅仅因为注入了IBackupCoordinator 服务就需要同时使用这两种操作。唯一可能遇到麻烦的地方是在跨不同服务的 DI 配置中使用命名实例。

选项 3

与选项 2 非常相似,您可以使用 Abstract Factory 的特殊形式来协调具体的 IDatabaseExporterIBackupMaker 之间的关系,这将充当依赖关系协调者的角色。


优点

  1. 需要维护的类型很少。
  2. 在 DI 容器中只需注册 1 个依赖项,使其更易于处理。
  3. 将生命周期管理移到 BackupMaker 服务中,这样就不可能以会导致内存泄漏的方式错误配置 DI。
  4. 显式实现您缺少的域概念,即依赖关系的协调。

缺点

  1. 将压缩排除在特定实现之外需要您实现 Null 对象模式。
  2. DI 容器不负责生命周期管理,每个依赖实例都是每个请求,这可能并不理想。
  3. 如果您的服务有很多依赖项,则通过 CoordinationFactory 实现的构造函数注入它们可能会变得笨拙。

接口

我正在为每种类型展示带有Release 方法的工厂实现。这是为了遵循Register, Resolve, and Release pattern,它可以有效地清理依赖项。如果第 3 方可以实现 ICompressorIDatabaseExporter 类型,这一点变得尤为重要,因为不知道他们可能需要清理哪些类型的依赖项。

但请注意,Release 方法的使用对于此模式是完全可选的,排除它们将大大简化设计。

public interface IBackupCoordinationFactory
{
    ICompressor CreateCompressor();
    void ReleaseCompressor(ICompressor compressor);
    IDatabaseExporter CreateDatabaseExporter();
    void ReleaseDatabaseExporter(IDatabaseExporter databaseExporter);
}

public interface IBackupMaker
{
    void Backup();
}

public interface IDatabaseExporter
{
    void Export(byte[] data);
}

public interface ICompressor
{
    byte[] Compress(byte[] data);
}

BackupCoordinationFactory 实现

public class BZip2ToMySqlBackupCoordinationFactory : IBackupCoordinationFactory
{
    public ICompressor CreateCompressor()
    {
        return new BZip2Compressor();
    }

    public void ReleaseCompressor(ICompressor compressor)
    {
        IDisposable disposable = compressor as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    public IDatabaseExporter CreateDatabaseExporter()
    {
        return new MySqlDatabseExporter();
    }

    public void ReleaseDatabaseExporter(IDatabaseExporter databaseExporter)
    {
        IDisposable disposable = databaseExporter as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

public class GZipToSqlServerBackupCoordinationFactory : IBackupCoordinationFactory
{
    public ICompressor CreateCompressor()
    {
        return new GZipCompressor();
    }

    public void ReleaseCompressor(ICompressor compressor)
    {
        IDisposable disposable = compressor as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    public IDatabaseExporter CreateDatabaseExporter()
    {
        return new SqlServerDatabseExporter();
    }

    public void ReleaseDatabaseExporter(IDatabaseExporter databaseExporter)
    {
        IDisposable disposable = databaseExporter as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

BackupMaker 实施

public class BackupMaker : IBackupMaker
{
    private readonly IBackupCoordinationFactory backupCoordinationFactory;

    public BackupMaker(IBackupCoordinationFactory backupCoordinationFactory)
    {
        this.backupCoordinationFactory = backupCoordinationFactory;
    }

    public void Backup()
    {
        // Get the data from somewhere
        byte[] data = new byte[0];

        // Compress the data
        byte[] compressedData;
        ICompressor compressor = this.backupCoordinationFactory.CreateCompressor();
        try
        {
            compressedData = compressor.Compress(data);
        }
        finally
        {
            this.backupCoordinationFactory.ReleaseCompressor(compressor);
        }

        // Backup the data
        IDatabaseExporter exporter = this.backupCoordinationFactory.CreateDatabaseExporter();
        try
        {
            exporter.Export(compressedData);
        }
        finally
        {
            this.backupCoordinationFactory.ReleaseDatabaseExporter(exporter);
        }
    }
}

选项 4

BackupMaker 类中创建一个保护子句,以防止允许不匹配的类型,并在它们不匹配的情况下抛出异常。

在 C# 中,您可以使用属性(将自定义元数据应用于类)来执行此操作。其他平台可能支持此选项,也可能不支持。


优点

  1. 无缝 - 无需在 DI 中配置额外的类型。
  2. 如果需要,可以扩展用于比较类型是否匹配的逻辑以包括每个类型的多个属性。例如,一个压缩器可以用于多个数据库。
  3. 100% 无效的 DI 配置会导致错误(尽管您可能希望让异常指定如何使 DI 配置工作)。

缺点

  1. 在特定备份配置之外保留压缩需要您实施 Null 对象模式。
  2. 用于比较类型的业务逻辑是在静态扩展方法中实现的,这使其可测试但无法与其他实现交换。
  3. 如果重构设计以使 ICompressorIDatabaseExporter 不是同一服务的依赖项,这将不再有效。

自定义属性

在 .NET 中,属性可用于将元数据附加到类型。我们制作了一个自定义的DatabaseTypeAttribute,我们可以将数据库类型名称与两种不同的类型进行比较,以确保它们兼容。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public DatabaseTypeAttribute : Attribute
{
    public DatabaseTypeAttribute(string databaseType)
    {
        this.DatabaseType = databaseType;
    }

    public string DatabaseType { get; set; }
}

具体的ICompressorIDatabaseExporter 实现

[DatabaseType("MySql")]
public class MySqlDatabaseExporter : IDatabaseExporter
{
    public void Export(byte[] data)
    {
        // implementation
    }
}

[DatabaseType("SqlServer")]
public class SqlServerDatabaseExporter : IDatabaseExporter
{
    public void Export(byte[] data)
    {
        // implementation
    }
}

[DatabaseType("MySql")]
public class BZip2Compressor : ICompressor
{
    public byte[] Compress(byte[] data)
    {
        // implementation
    }
}

[DatabaseType("SqlServer")]
public class GZipCompressor : ICompressor
{
    public byte[] Compress(byte[] data)
    {
        // implementation
    }
}

扩展方法

我们将比较逻辑滚动到扩展方法中,因此IBackupMaker 的每个实现都会自动包含它。

public static class BackupMakerExtensions
{
    public static bool DatabaseTypeAttributesMatch(
        this IBackupMaker backupMaker, 
        Type compressorType, 
        Type databaseExporterType)
    {
        // Use .NET Reflection to get the metadata
        DatabaseTypeAttribute compressorAttribute = (DatabaseTypeAttribute)compressorType
            .GetCustomAttributes(attributeType: typeof(DatabaseTypeAttribute), inherit: true)
            .SingleOrDefault();

        DatabaseTypeAttribute databaseExporterAttribute = (DatabaseTypeAttribute)databaseExporterType
            .GetCustomAttributes(attributeType: typeof(DatabaseTypeAttribute), inherit: true)
            .SingleOrDefault();

        // Types with no attribute are considered invalid even if they implement
        // the corresponding interface
        if (compressorAttribute == null) return false;
        if (databaseExporterAttribute == null) return false;

        return (compressorAttribute.DatabaseType.Equals(databaseExporterAttribute.DatabaseType);
    }
}

BackupMaker 实施

保护子句确保在创建类型实例之前拒绝具有不匹配元数据的 2 个类。

public class BackupMaker : IBackupMaker
{
    private readonly ICompressor compressor;
    private readonly IDatabaseExporter databaseExporter;

    public BackupMaker(ICompressor compressor, IDatabaseExporter databaseExporter)
    {
        // Guard to prevent against nulls
        if (compressor == null)
            throw new ArgumentNullException("compressor");
        if (databaseExporter == null)
            throw new ArgumentNullException("databaseExporter");

        // Guard to prevent against non-matching attributes
        if (!DatabaseTypeAttributesMatch(compressor.GetType(), databaseExporter.GetType()))
        {
            throw new ArgumentException(compressor.GetType().FullName + 
                " cannot be used in conjunction with " + 
                databaseExporter.GetType().FullName)
        }

        this.compressor = compressor;
        this.databaseExporter = databaseExporter;
    }

    public void Backup()
    {
        // Get the data from somewhere
        byte[] data = new byte[0];

        // Compress the data
        byte[] compressedData = this.compressor.Compress(data);

        // Backup the data
        this.databaseExporter.Export(compressedData);

    }
}

如果您决定选择其中一个选项,如果您留下评论以说明您选择哪一个,我将不胜感激。我在我的一个项目中遇到了类似的情况,我倾向于选项 2。

响应您的更新

是非常具体的命名和如此粗略的合同,还是我可以做得更好?我应该把合同测试变成集成测试吗?也许(集成)测试所有三个的组成?我并不是真的想成为通用的,而是试图将职责分开并保持可测试性。

创建集成测试是个好主意,但前提是您确定自己正在测试生产 DI 配置。尽管将所有这些作为一个单元进行测试以验证其是否有效也很有意义,但如果配置了随附的代码,对于此用例对您没有多大帮助与测试不同。

你应该具体吗?我相信我已经在这件事上给了你一个选择。如果您使用保护条款,则根本不必具体。如果您选择其他选项之一,则可以在特定和通用之间取得很好的折衷。

我知道您说过您不是故意尝试通用的,最好在某处划清界限以确保解决方案不会过度设计。另一方面,如果由于接口不够通用而必须重新设计解决方案,那也不是一件好事。无论是否预先指定,可扩展性始终是一项要求,因为您永远不知道未来业务需求将如何变化。因此,拥有一个通用的 BackupMaker 绝对是最好的方法。其他类可以更具体 - 如果未来需求发生变化,您只需要一个接缝来交换实现。

【讨论】:

  • 非常感谢您的贡献。它帮助我朝着正确的方向前进。我更新了我的问题,以便更好地阐明它对我的真正意义。
  • 内容丰富、全面。如果 op 真的需要这样一个可插入的框架,AggregateService(或 Facade Pattern)是一种干净的方法。您的所有建议都很好。
【解决方案2】:

我的第一个建议是批判性地思考您是否需要如此通用:您有一个具体的问题要解决,您希望将一个非常特定的数据库备份为特定的格式。通过解决任意数据库和任意格式的问题,您有什么好处吗?您肯定会从通用解决方案中得到样板代码和增加的复杂性(人们了解具体问题,而不是通用问题)。 如果这适用于您,那么我的建议是不要让您的 DatabaseExporter 接受接口,而只接受具体的实现。那里有足够多的现代工具,它们也可以让你模拟具体的类,所以可测试性也不是在这里使用接口的论据。

另一方面,如果您确实必须使用不同的策略备份多个数据库,那么我可能会引入类似

class BackupPlan { public DatabaseExporter exporter() {/**...*/} public Compressor compressor() {/** ... */} }

然后您的 BackupMaker 将通过一个 BackupPlan,指定要使用哪种算法压缩哪个数据库。

【讨论】:

  • 务实简单。本质上,是对 YAGNI 原则的陈述。 +1
【解决方案3】:

您的问题是强调对象组合非常重要,并且负责这种组合(接线)的实体负有重大责任。

由于您已经拥有一个通用的 BackupMaker,我建议您保持这种方式,并推动确保在composition root 中完成正确的对象组合(以解决特定问题)的重大责任。

您的应用程序源代码的读者(您和您的团队成员)将有一个地方(组合根)来了解您如何组合对象以使用通用类(例如 BackupMaker)来解决您的特定问题。

换句话说,组合根是您决定细节的地方。它是您使用泛型创建特定的地方。

回复评论:

哪个应该知道那些依赖关系?

组合根需要了解所有内容(所有依赖项),因为它正在创建应用程序中的所有对象并将它们连接在一起。组合根知道每个拼图的作用,并将它们连接在一起以创建一个有意义的应用程序。

对于 BackupMaker,它应该只关心能够履行其单一职责的程度。在您的示例中,它的单一(简单)职责(在我看来)是协调其他对象的消耗以创建备份。

只要您使用 DI,类就永远无法确定其协作者的行为是否正确,只有组合根才能确定。考虑这个简单而极端的 IDatabaseExporter 实现示例(假设开发人员实际上给了这个类这个名称,并且他故意这样实现它):

public class StupidDisastrousDatabaseExporter : IDatabaseExporter
{
    public ExportedData Export()
    {
        DoSomethingStupidThatWillDeleteSystemDataAndMakeTheEnterpriseBroke();

        ...
    }

    private void DoSomethingStupidThatWillDeleteSystemDataAndMakeTheEnterpriseBroke()
    {
        //do it
        ...
    }
}

现在,BackupMaker 永远不会知道它正在消耗一个愚蠢而灾难性的数据库导出器,只有组合根会这样做。我们永远不能因为这个灾难性的错误而责怪编写 BackupMaker 类的程序员(或设计 IDatabaseExporter 合约的程序员)。但是,如果将 StupidDisastrousDatabaseExporter 实例注入到 BackupMaker 的构造函数中,则在组合根中编写应用程序的程序员会受到指责。

当然,一开始就不应该编写 StupidDisastrousDatabaseExporter 类,但我给你举了一个极端的例子,向你展示合约(接口)永远不能(也不应该)保证每个 关于其实现者的方面。应该说够了。

有没有办法表达 IDatabaseExporter 以保证此类接口的实现者不会做出愚蠢或灾难性的行为?没有。

请注意,虽然 BackupMaker 处理的是合约(没有 100% 保证),但组合根实际上处理的是具体的实现类。这赋予了它巨大的权力(因此也赋予了巨大的责任)来保证正确的对象图的组合。

如何确保我以合理的方式作曲?

您应该为组合根创建的对象图创建自动化的端到端测试。在这里,您要确保组合根已经完成了以正确方式组合对象的重大责任。在这里您可以测试您想要的确切详细信息(例如备份结果采用某种确切的格式/详细信息)。

查看this article,了解使用组合根进行自动化测试的方法。

【讨论】:

  • 您的回答说明BackupMaker 与其依赖关系的组成部分之间存在着斗争:应该知道这些依赖关系是什么?问题仍然是我如何确保我以合理的方式作曲?另请参阅我的问题的重新措辞。
  • @Thijs van Dien,在答案中查看我的回复
【解决方案4】:

我相信这可能是当过于关注对象模型而排除功能组合时出现的问题。考虑一个简单的函数分解(函数如 f : a -> b)的第一步:

exporter: data -> (format, memory), or exception

compressor: memory -> memory, or exception

writer: memory -> side-effect, or exception 

backup-maker: (data, exporter, compressor, writer) -> backup-result

所以最后一个函数 backup-maker 可以使用这三个函数进行参数化,假设我已经正确考虑了您的用例,并且如果三个参数具有相同的输入和输出类型,例如格式和内存,尽管它们已实现。

现在,“胆量”,或可能的备份制造商分解(从右到左阅读),绑定所有函数,以 data 作为参数,并使用组合运算符 “。”

备份制造商:中间计算。作家。中间计算。压缩机。中间计算。出口商

我特别想指出的是,这种架构模型以后可以表示为对象接口,也可以表示为一等函数,例如c++ std::function.

编辑:它也可以细化为泛型术语,其中内存是泛型类型参数,以在需要的地方提供类型安全。例如。

backup-maker<type M>: (data, exporter<M>, compressor<M>, writer<M>) -> ..

更多关于函数分解的技术和好处的信息可以在这里找到: http://jfeltz.com/posts/2015-08-30-cost-decreasing-software-architecture.html

【讨论】:

  • 在您的功能方法中,我的问题仍然存在。如果backup-maker 被赋予随机的compressorbackup-result 将无效。它不仅需要任何功能memory -&gt; memory;生成的内存需要遵循一个非常特定的模式,该模式取决于进入的内存。我的问题是我应该如何处理这种情况。如何使backup-maker 被错误的compressor(或exporter)变得不可能或至少不太可能?它希望规定其合作者实施的一个方面,同时对其他方面不可知论。如何编码?
  • “一种特定的记忆模式”可以被认为是一种类型吗?如果可以,那么您是否将内存视为类型参数?例如backup-maker : (data, exporter,compressor,writer).
  • 并非如此,因为描述一段数据所需的压缩输出基本上意味着实现整个算法。
  • 这很难相信,因为类型基本上可以覆盖一系列整数,甚至可以在矩阵中传递。例如。枚举 M { 算法 1 |算法 2 .. },带有压缩器 { impl.对于 }。我也看不出您如何无法通过测试检查实现 的每个方法的前置条件和后置条件,或者在小案例上进行合同。要么是这个,要么你真的不想要类型安全。
【解决方案5】:

您的要求似乎矛盾:

  • 您想要具体(只允许一个子集(或只允许一个?)的组合)
  • 但您还希望通过使用接口、DI 等来实现泛型。

我的建议是在您的代码发展之前保持简单(在您的情况下,这意味着不要尝试通用)。

当您的代码将进化时,以更通用的方式重构。下面的代码显示了通用/特定之间的折衷:

public interface ICompressor {
    public byte[] compress(byte[] source); //Note: the return type and type argument may not be revelant, just for demonstration purpose
}

public interface IExporter {
    public File export(String connectionString); //Note: the return type and type argument may not be revelant, just for demonstration purpose
}

public final class Bzip2 implements ICompressor {
    @Override
    public final byte[] compress(byte[] source) {
        //TODO
    }
}

public final class MySQL implements IExporter {
    @Override
    public final File export(String connnectionString) {
        //TODO
    }
}


public abstract class ABackupStrategy {
    private final ICompressor compressor;
    private final IExporter exporter;

    public ABackupStrategy(final ICompressor compressor, final IExporter exporter) {
        this.compressor = compressor;
        this.exporter = exporter;
    }

    public final void makeBackup() {
        //TODO: compose with exporter and compressor to make your backup
    }
}

public final class MyFirstBackupStrategy extends ABackupStrategy {
    public MyFirstBackupStrategy(final Bzip2 compressor, final MySQL exporter) {
        super(compressor, exporter);
    }
}

使用ICompressorIExporter,您可以轻松添加其他压缩算法,从中导出其他数据库。

使用ABackupStrategy,您可以通过继承轻松定义新的允许的具体压缩器/导出器组合。

缺点:我必须在不声明任何 abstract 方法的情况下创建 ABackupStrategy abstract,这与 OOP 原则相矛盾。

【讨论】:

  • 我不想过于笼统,也不想过于具体。另请参阅我的问题的重新措辞。
猜你喜欢
  • 1970-01-01
  • 2011-01-25
  • 2019-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多