Skip to content

Instantly share code, notes, and snippets.

  • Save eterekhin/1e395b47c247ac8505a8713170564e98 to your computer and use it in GitHub Desktop.
Save eterekhin/1e395b47c247ac8505a8713170564e98 to your computer and use it in GitHub Desktop.
Fowler.Extract Subclass.Extract Superclass.Extract Interface.Collapse Hierarchy.Form Template Method. Replace Inheritance with Delegation.Replace Delegation with Inheritance

Extract Subclass

Применяется когда класс стал слишком большим и выпонять слишком много действий, тогда самое просто решение вынести действия которые он не должен выполнять в отдельный класс и в исходном классе использовать его

 public class Person
    {
        private string Name { get; }
        private string Surname { get; }

        private IEnumerable<Salary> Salaries { get; }

        public decimal GetSalary()
        {
            return Salaries.Sum(x => x.Value);
        }
    }

Если класс Person не должен уметь считать свою суммарную зарплату:

 public class Person
    {
        public string Name { get; }
        public string Surname { get; }
        private SalaryCalculator Calculator { get; }

        public decimal GetSalary()
        {
            return Calculator.GetSalary();
        }
    }
    
    public class SalaryCalculator
    {
        private IEnumerable<Salary> Salaries { get; }

        public decimal GetSalary()
        {
            return Salaries.Sum(x => x.Value);
        }
    }

Extract Superclass

Применяется при рефакторингах Replace Code Type with strategy/state, Replace Code Type on Subclasses Например, я проектировал класс SqlCommand, в котором хранилась информация о параметрах, времени выполнения, sql запросе, и из нее нужно было получать sql запроса, но потом появилась SqlCommand'a для другой базы данных, из которой также нужно получать sql строку запроса,которая формируется по-разному, поэтому я выделяю базовый класс SqlCommand, и делаю два наследника MsSqlCommand, PostgresSqlCommand, класс SqlCommand становится абстрактным, логика формирования sql запроса перемещается в наследников Было:

 public class SqlCommand
    {
        private readonly string _value;
        private readonly Dictionary<string, string> _parameters;

        public SqlCommand(string sqlCommand)
        {
            _value = GetSqlRequest(sqlCommand);
            _parameters = GetParametersDictionary(sqlCommand);
        }

        private string GetSqlRequest(string sqlcommand) => "not implemented";

        private Dictionary<string, string> GetParametersDictionary(string sqlcommand)
            => new Dictionary<string, string>
            {
                {"not", "implemented"}
            };
    }

Стало:

public abstract class SqlCommand
    {
        protected readonly string _value;
        private readonly Dictionary<string, string> _parameters;

        public SqlCommand(string sqlCommand)
        {
            _value = GetSqlRequest(sqlCommand);
            _parameters = GetParametersDictionary(sqlCommand);
        }

        private string GetSqlRequest(string sqlcommand) => "not implemented";

        private Dictionary<string, string> GetParametersDictionary(string sqlcommand)
            => new Dictionary<string, string>
            {
                {"not", "implemented"}
            };

    }

    class PostgresCommand : SqlCommand
    {
        public PostgresCommand(string sqlCommand) : base(sqlCommand)
        {
        }
        public string GetRequestSql() => _value.ToLower();
    }

    class MsSqlCommand : SqlCommand
    {
        public MsSqlCommand(string sqlCommand) : base(sqlCommand)
        {
        }
        public string GetRequestSql() => _value.ToUpper();
    }

Extract Interface

Интерфейсы нужны чтобы декларировать фунциональность которой обладает класс, например класс умеет вычислять квадратный корень, и есть клиенты которым нужна функциональность вычисления квадратного корня, тогда пусть он явно говорить о том, что умеет вычислять квадратный корень

  public interface ICanCalculateSquareRoot
    {
        double SquareRoot(int a);
    }

    public interface ICanRaiseNumberToPower
    {
        int RaiserNumberToPower(int number, int power);
    }

    public class Calculator : ICanRaiseNumberToPower, ICanCalculateSquareRoot
    {
        public double SquareRoot(int a) => Math.Sqrt(a);
        public int RaiserNumberToPower(int number, int power) => (int) Math.Pow(number, power);
    }

Теперь если разным клиентам нужны функции вычисления корня и возведения в степень, они получат разные интерфейсы, вместо того чтобы получить полный объект. Интерфейсы очень ценны тем, что позволяют инвертировать зависимости, например было так:

    //Domain
    public class Order
    {
        public decimal Value { get; set; }
    }
    //
    
    // Data Access Layer
    public class OrderRepository
    {
        public IEnumerable<Order> GetOrders() => Enumerable.Empty<Order>();
    }
    //
    
    // Application Layer
    public class OrderCalculator
    {
        private readonly OrderRepository _orderRepository;

        public OrderCalculator(OrderRepository orderRepository)
        {
            _orderRepository = orderRepository;
        }

        public decimal Calculate()
        {
            return _orderRepository.GetOrders().Sum(x => x.Value);
        }
    }
    //

Application Layer зависит от Data Access Layer, т.е если мы захоти переехать на другую ORM, но придется изменять AppLayer, но можно сделать так:

  //Domain
    public class Order
    {
        public decimal Value { get; set; }
    }
    //
    
    // Data Access Layer
    public interface IOrderRepository
    {
        IEnumerable<Order> GetOrders();
    }
    public class OrderRepository:IOrderRepository
    {
        public IEnumerable<Order> GetOrders() => Enumerable.Empty<Order>();
    }
    //
    
    // Application Layer
    public class OrderCalculator
    {
        private readonly IOrderRepository _orderRepository;

        public OrderCalculator(IOrderRepository orderRepository)
        {
            _orderRepository = orderRepository;
        }

        public decimal Calculate()
        {
            return _orderRepository.GetOrders().Sum(x => x.Value);
        }
    }
    //

В Application Layer положим интерфейс IOrderRepository, и реализуем его в DataAccessLayer в классе OrderRepository, в результате получили DAL зависит от Application Layer, а не наоборот

Collapse Hierarchy

Применяется когда ранее задуманная иерархия оказалась ненужной, тогда, чтобы избежать ненужной сложности выбрасывает наследников и свёртываем иерархию в один класс

Form Template Method

Если в базовом классе есть метод, который подразделяется внутри на несколько шагов, которые различны в наследниках, тогда эти шаги нужно вынести в методы, их сделать абстрактными в базовом классе( и базовый класс, также абстрактным) и реализовать новые методы в наследниках.

Replace Inheritance with Delegation

Если для какого-то наследника хоть один метод в базовом классе не релевантен, то нужно заменить наследование на делегирование, теперь класс будет декларировать только нужные методы.Либо можно выделить родителя у базового класса, и наследование на этот базовый класс

Replace Delegation with Inheritance

Такое может случиться если раньше базовый класс раньше брал на себя слишком много, и теперь избавился от некоторых обязанностей, и теперь все методы базового класса становятся релевантными для классов, которые раньше делегировали Также такое может случиться, если изменилась семантика какого-то наследника, но такое бывает реже

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment