Принцип был сформулирован Бертраном Мейером в 1988 году:
Программные сущности должны быть открыты для расширения и закрыты для изменения.1
Иными словами, должна иметься возможность расширять поведение программных сущностей без их изменения1.
Добиться такого поведения можно, например, за счет паттерна стратегии или шаблонного метода.
Возьмем, например, какой-нибудь класс выполняющий логику в зависимости от входного параметра в некий метод. Пусть, например, это будет загрузка данных. При появлении нового источника загрузки придется менять код класса и тестировать все случаи, что неудобно будет делать каждый раз. По сути механизм загрузки не связан с каким то конкретным источником, поэтому можно источники вынести в стратегию, а механизм загрузки оставить неизменным. Теперь чтобы добавить новый источник надо добавить новую реализацию стратегии и передать ее в механизм загрузки. Получать конкретный тип можно через фабрику, передавая туда то что раньше передавали в некий метод.
Тоже самое можно сделать и через шаблонный метод. Логика загрузки в базовом классе, а источники в шаблонных методах подкласса.
После такого рефакторинга добавлять новые источники и тестировать их будет гораздо легче, ведь старый код не изменялся, а только добавились новые реализации.
Код до рефакторинга:
        public async Task<ResponseModel> LoadData(string source, CancellationToken cancellationToken)
        {
            ResponseModel? responseModel = null;
            _logger.LogInformation(1084061059, "source is {source}", source);
            if (source == "source1")
            {
                var response = await _source1.LoadData1(cancellationToken);
                responseModel = _mapper.Map<Source1Response, ResponseModel>(response);
            }
            else if (source == "source2")
            {
                var response = await _source2.LoadData2(cancellationToken);
                responseModel = _mapper.Map<Source2Response, ResponseModel>(response);
            }
            else if (source == "source3")
            {
                var response = await _source3.LoadData3(cancellationToken);
                responseModel = _mapper.Map<Source3Response, ResponseModel>(response);
            }
            //logic
            return responseModel ?? throw new NotImplementedException(source);
        }Рефакторинг через стратегию
Основной механизм:
        public async Task<ResponseModel> LoadData(ISource source, CancellationToken cancellationToken)
        {
            _logger.LogInformation(1084061059, "source is {source}", source);
            var responseModel = await source.LoadData(cancellationToken);
            
            //logic
            return responseModel;
        }Стратегия:
    public interface ISource
    {
        Task<ResponseModel> LoadData(CancellationToken cancellationToken);
    }Одна из реализаций источника:
    public class Source1 : ISource
    {
        public Task<ResponseModel> LoadData(CancellationToken cancellationToken) => //load logic;
    }Фабрика:
        public ISource SourceFactory(string source) =>
            source switch
            {
                "source1" => new Source1(),
                "source2" => new Source2(),
                "source3" => new Source3(),
                _ => throw new ArgumentOutOfRangeException(nameof(source), source, null)
            };Использование:
            var source = SourceFactory("source1");
            var response = await LoadData(source, CancellationToken.None);Рефакторинг через шаблонный метод
Основной механизм в абстрактном классе:
        public abstract class Source
        {
            public abstract string Name { get; }
            protected abstract Task<ResponseModel> LoadDataFromSource(CancellationToken cancellationToken);
            public async Task<ResponseModel> LoadData(CancellationToken cancellationToken)
            {
                var responseModel = await LoadDataFromSource(cancellationToken);
                //logic
                
                return responseModel;
            }
        }Источники реализующие абстрактный Name и LoadDataFromSource :
        public class SourceClass1 : Source
        {
            public override string Name => "source1";
            protected override Task<ResponseModel> LoadDataFromSource(CancellationToken cancellationToken)
            {
                //load logic           
            }
        }
        public class SourceClass2 : Source
        {
            public override string Name => "source2";
            protected override Task<ResponseModel> LoadDataFromSource(CancellationToken cancellationToken)
            {
                //load logic             
            }
        }Фабрика:
        private List<Source> _sources = new()
        {
            new SourceClass1(),
            new SourceClass2(),
            new SourceClass3()
        };
        public Source SourceFactoryMethod2(string source) =>
            _sources.FirstOrDefault(a => a.Name == source) ??
            throw new ArgumentOutOfRangeException(nameof(source), source, null);Использование:
            var source = SourceFactoryMethod2("source1");
            var response = await source.LoadData(CancellationToken.None);
Примечания
1. Мартин Р. Чистая архитектура. Искусство разработки программного обеспечения. — СПб.: Питер, 2018.