重构使代码对扩展开放但对修改关闭

Refactoring to make code open for extensions but closed for modifications

为了我的项目目的,我需要将指标发送到 AWS。

我有主要 class 调用 SendingMetrics。

private CPUMetric _cpuMetric;
private RAMMetric _ramMetric;
private HDDMetric _hddMetric;
private CloudWatchClient _cloudWatchClient(); //AWS Client which contains method Send() that sends metrics to AWS

public SendingMetrics()
{
    _cpuMetric = new CPUMetric();
    _ramMetric = new RAMMetric();
    _hddMetric = new HDDMetric();
    _cloudwatchClient = new CloudwatchClient();
    InitializeTimer();
}

private void InitializeTimer()
{
   //here I initialize Timer object which will call method SendMetrics() each 60 seconds.
}

private void SendMetrics()
{
    SendCPUMetric();
    SendRAMMetric();
    SendHDDMetric();
}

private void SendCPUMetric()
{
    _cloudwatchClient.Send("CPU_Metric", _cpuMetric.GetValue());
}

private void SendRAMMetric()
{
    _cloudwatchClient.Send("RAM_Metric", _ramMetric.GetValue());
}

private void SendHDDMetric()
{
    _cloudwatchClient.Send("HDD_Metric", _hddMetric.GetValue());
}

我还有 CPUMetric、RAMMetric 和 HDDMetric classes,它们看起来非常相似,所以我只显示一个 class.

的代码
internal sealed class CPUMetric
{
    private int _cpuThreshold;

    public CPUMetric()
    {
        _cpuThreshold = 95;
    }

    public int GetValue()
    {
        var currentCpuLoad = ... //logic for getting machine CPU load
        if(currentCpuLoad > _cpuThreshold)
        {
             return 1;
        }
        else 
        {
             return 0;
        }
    }
}

所以我的问题是在我的示例中不满足干净的编码。我有 3 个指标要发送,如果我需要引入新指标,我将需要创建新的 class,在 SendingMetrics class 中对其进行初始化并修改 class,这不是我想要的.我想满足Open Closed原则,所以它对扩展开放但对修改关闭。

正确的做法是什么?我会将那些发送方法(SendCPUMetric、SendRAMMetric、SendHDDMetric)移动到相应的 classes(SendCPUMetric 方法到 CPUMetric class、SendRAMMEtric 到 RAMMetric 等)但是如何修改 SendingMetrics class 所以它因修改而关闭,如果我需要添加新指标以不更改 class。

您的设计几乎是正确的。您有 3 个数据检索器和 1 个数据发送器。因此很容易添加更多指标(更多检索器)(对扩展开放)而不影响当前指标(对修改关闭),您只需要多一点重构来减少重复代码。

而不是有 3 个指标 class 看起来非常相似。只有下一行不同

var currentCpuLoad = ... //logic for getting machine CPU load

您可以像这样创建通用指标

internal interface IGetMetric
{
    int GetData();
}

internal sealed class Metric
{
    private int _threshold;
    private IGetMetric _getDataService;

    public Metric(IGetMetric getDataService)
    {
        _cpuThreshold = 95;
        _getDataService = getDataService;
    }

    public int GetValue()
    {
        var currentCpuLoad = _getDataService.GetData();
        if(currentCpuLoad > _cpuThreshold)
        {
             return 1;
        }
        else 
        {
             return 0;
        }
    }
}

然后只需创建 3 个 GetMetric classes 来实现该接口。这只是减少代码重复的一种方法。你也可以使用继承(但我不喜欢继承)。或者您可以使用 Func 参数。

更新:添加 class 以获得 CPU 指标

internal class CPUMetricService : IGetMetric
{
    public int GetData() { return ....; }
}
internal class RAMMetricService : IGetMetric
{
    public int GetData() { return ....; }
}
public class AllMetrics
{
    private List<Metric> _metrics = new List<Metric>()
    {
         new Metric(new CPUMetricService());
         new Metric(new RAMMetricService());
    }

    public void SendMetrics()
    {
         _metrics.ForEach(m => ....);
    }
}

在像 C# 这样的面向对象语言中,开闭原则 (OCP) 通常是通过使用多态性的概念来实现的。也就是说,同类对象对同一条消息的反应不同。查看您的 class "SendingMetrics" 很明显 class 可以与不同类型的 "Metrics" 一起使用。好消息是您的 class "SendingMetrics" 通过发送消息 "getData" 以相同的方式与所有类型的指标对话。因此,您可以通过创建由具体类型的指标实现的接口 "IMetric" 来引入新的抽象。这样你就可以将 "SendingMetrics" class 与具体的度量类型分离,这意味着 class 不知道具体的度量类型。它只知道 IMetric 并以相同的方式对待它们,这使得添加任何实现 IMetric 接口(开放扩展)的新协作者(度量类型)成为可能,而无需更改 "SendingMetrics" class(关闭修改)。这也要求不同类型指标的对象不是在 "SendingMetrics" class 中创建的,而是例如由工厂或 class 外部并作为 IMetrics 注入。

除了使用继承来启用多态性并通过引入接口 IMetric 实现 OCP 之外,您还可以使用继承来消除冗余。这意味着您可以为所有度量类型引入一个抽象基础 class,实现所有类型度量使用的共同行为。