Рефакторинг, чтобы сделать код открытым для расширений, но закрытым для изменений - PullRequest
1 голос
/ 23 марта 2020

Для целей моего проекта мне нужно отправить метрики на AWS.

У меня есть основной класс с именем 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());
}

Также у меня есть CPUMetri c, RAMMetri c и HDDMetri c классы, которые выглядят очень похоже, поэтому я просто покажу код одного класса.

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 метрики для отправки, и если мне нужно будет ввести новый показатель c, мне нужно будет создать новый класс, инициализировать его в классе SendingMetrics и изменить этот класс, а это не то, что мне нужно. Я хочу удовлетворить принцип Open Closed, поэтому он открыт для расширений, но закрыт для изменений.

Как правильно это сделать? Я бы переместил эти методы отправки (SendCPUMetri c, SendRAMMetri c, SendHDDMetri c) в соответствующие классы (метод SendCPUMetri c в класс CPUMetri c, SendRAMMEtri c в RAMMetri c, et c), но как изменить класс SendingMetrics, чтобы он был закрыт для изменений, и если мне нужно добавить новую метри c, чтобы не изменять этот класс.

Ответы [ 2 ]

0 голосов
/ 27 марта 2020

В объектно-ориентированных языках, таких как C# Открытый закрытый принцип (OCP) обычно достигается с помощью концепции полиморфизма. То есть объекты одного типа реагируют по-разному на одно и то же сообщение. Глядя на ваш класс «SendingMetrics», становится очевидным, что класс работает с разными типами «Metrics». Хорошо, что ваш класс SendingMetrics общается со всеми типами метрик одинаково, отправляя сообщение «getData». Следовательно, вы можете ввести новую абстракцию, создав интерфейс «IMetri c», который реализуется конкретными типами метрик. Таким образом, вы отделяете свой класс «SendingMetrics» от конкретных типов metri c, что означает, что класс не знает о конкретных c metri c типах. Он знает только IMetri c и обрабатывает их все одинаково, что позволяет добавлять любого нового соавтора (тип metri c), который реализует интерфейс IMetri c (открытый для расширения) без необходимости изменения класс "SendingMetrics" (закрыт для модификации). Это также требует, чтобы объекты различных типов метрик создавались не в классе «SendingMetrics», а, например, фабрикой или вне класса и вводились как IMetrics.

Помимо использования наследования для включения полиморфизма и достижения OCP с помощью интерфейса IMetri c, вы также можете использовать наследование для удаления избыточности. Это означает, что вы можете ввести абстрактный базовый класс для всех типов metri c, который реализует общее поведение, используемое всеми типами метрик.

0 голосов
/ 23 марта 2020

Ваш дизайн почти правильный. Вы получили 3 данных и 1 отправитель данных. Так что легко добавить больше метри c (больше ретривера) (открыто для расширений), не влияя на текущие метрики (закрыто для изменений), вам просто нужно немного больше рефакторинга, чтобы уменьшить дублирующийся код.

Вместо того, чтобы иметь 3 класса метрик выглядят очень похоже. Только нижняя строка отличается

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

Вы можете создать шаблон c metri c следующим образом

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 класса GetMetri c для реализации этого интерфейса , Это всего лишь 1 способ уменьшить дублирование кода. Вы также можете использовать наследование (но я не люблю наследование). Или вы можете использовать Fun c param.

UPDATED: добавлен класс для получения метрики ЦП c

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 => ....);
    }
}
...