Рефакторинг при котором метод в одном классе больше интересуется другим классом, чем своим собственным, наглядный пример:
public class User
{
public Account Account { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public void PrintSalary()
{
var currentMonth = MonthExtensions.MonthByDateTime(DateTime.UtcNow);
switch (Account.AccountType)
{
case Status.Contractor:
Console.WriteLine(Account.SalaryByMonth[currentMonth]);
break;
case Status.Worker:
Console.WriteLine(Account.SalaryByMonth[currentMonth] * 1.3m);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
public enum Status
{
Contractor,
Worker
}
public class Account
{
public int Id { get; set; }
public User User { get; set; }
public Status Status { get; set; }
public Dictionary<Month, decimal> SalaryByMonth { get; set; }
public Account(int id, User user, Status status)
{
Id = id;
User = user;
Status = status;
}
}
Метод PrintSalary не обращается к свойствам класса User, поэтому его нужно переместить в класс Account, к свойсвам которого идут постоянные обращения, а в классе User сделать делегирование(желательно найти всех клиентов и почистить все обращения к этому методу):
public class User
{
public Account Account { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public void PrintSalary()
{
Account.PrintSalary();
}
}
Очевидный рефаторинг применяется, когда в классе поле не используется в классе, и служит только для делегирования обязанностей или обращения клиентов
Выделение класса применяется если в классе есть методы/свойства которые ему не присущи, например у класса User из примера выше будет метод SetSalaryByMonth и свойство Salary, также как в нем был метод PrintSalary в Move Method. Extract класс это более широкий рефакторинг чем MoveMethod. Класс должен представлять ясную абстракция - то, для чего он был создан, иметь как можно меньше причин для изменения, поэтому если в какой-то его части возможны изменения и эта часть косвенно связяна с назначением класса, ее стоит выделить в отдельный класс
public class User
{
private string Name { get; set; }
private string Surname { get; set; }
private AccountType AccountType { get; set; }
private Dictionary<Month, decimal> SalaryByMonth { get; set; }
public void SetSalaryByMonth(Month month, decimal newSalary)
{
SalaryByMonth[month] = newSalary;
}
public void PrintSalary()
{
var currentMonth = MonthExtensions.MonthByDateTime(DateTime.UtcNow);
switch (AccountType)
{
case AccountType.Contractor:
Console.WriteLine(SalaryByMonth[currentMonth]);
break;
case AccountType.Worker:
Console.WriteLine(SalaryByMonth[currentMonth] * 1.3m);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
Класс User стал достаточно большим, и может изменяться при изменении зарплаты, типа аккаунта или учетных данных, вынесем первые две причины в отдельный класс, чтобы он зависел только от изменения учетных данных
public class User
{
private string Name { get; set; }
private string Surname { get; set; }
}
public class Account
{
private AccountType AccountType { get; set; }
private Dictionary<Month, decimal> SalaryByMonth { get; set; }
public void PrintSalary()
{
var currentMonth = MonthExtensions.MonthByDateTime(DateTime.UtcNow);
switch (AccountType)
{
case AccountType.Contractor:
Console.WriteLine(SalaryByMonth[currentMonth]);
break;
case AccountType.Worker:
Console.WriteLine(SalaryByMonth[currentMonth] * 1.3m);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public void SetSalaryByMonth(Month month, decimal newSalary)
{
SalaryByMonth[month] = newSalary;
}
}
Нужен, когда после предыдущего рефакторинга какой-то из классов лишился поведения, и его значимость в системе упала. Иногда можно оставлять класс без поведения если это, например, Value Object, но если там осталось всего несколько полей проще переместить эти поля в класс, в котором лежит ссылка на класс, котоорый мы хотим подвергнуть рефакторингу Было:
public class User
{
private string Name { get; set; }
private string Surname { get; set; }
private Salary Salary { get; set; }
}
public class Salary
{
public decimal SalaryValue { get; set; }
}
Стало:
public class User
{
private string Name { get; set; }
private string Surname { get; set; }
private decimal Salary { get; set; }
}
Сложность может возникнуть если у класса Salary много клиентов, тогда, рефакторинг стоит остановить Замечу, что данный рефакторинг довольно спорный, свойство имеющее тип Salary добавляет однозначности, нежели просто тип decimal, также в конструктор этого свойства можно вынести проверку на то, что сумма неотрицательна Поэтому я считаю, что этот рефакторинг бессмысленным, и применять его не советую
Прием позволяет усилить инкапсуляцию, пример:
public class User
{
private readonly int _id;
public Account Account { get; }
private string Name { get; set; }
private string Surname { get; set; }
}
Предположим, что у этого класса, много клиентов, обращающихся к нему за типом Account'a,
user.Account.AccountType
При этом клиенты получают доступ к целому типу Account, следовательно начинают зависеть от него( к примеру начнут использовать OrganizationType, а позже мы захотим заменить его на другой тип, придется проходится по всем использованиям класса account, и заменять в нем тип, или же мы может выдавать AccountType только через User'a в таком случае удастся поменять тип в классе Account, а в классе User сделать просто маппинг нового типа на старый, и только на следующем шаге начать выводить из обращения старый тип
public class User
{
private readonly int _id;
private Account Account { get; }
private string Name { get; set; }
private string Surname { get; set; }
public User(Account accountType, string name, string surname, int id)
{
_id = id;
Account = accountType;
Name = name;
Surname = surname;
}
public AccountType GetAccountType()
{
switch (Account.NewAccountType)
{
case NewAccountType.NewContractor:
return AccountType.Contractor;
case NewAccountType.NewWorker:
return AccountType.Worker;
default:
throw new ArgumentOutOfRangeException();
}
}
}
Нужен если один из классов занимается больше делегированием чем выполнением своих прямых функций, нужно сделать публичным свойство, которому все делегируется, заменить все вызовы клиентами делегирующих методов, прямыми вызовами методов открытого свойства, после этого удалить все делегирующие методы
В C# проще использовать Extensions методы (смотри следующий пункт)
В библиотечном класе нет методов, которые нам нужны В Csharp для таких целей могут служить Extension методы, к приемеру у тип Int нет метода Subtract;
public static class IntExtensions
{
public static int Subtract(this int a, int b)
{
return a - b;
}
}
теперь можно вызывать 1.Subtract(1)
Либо, если используется внедрение зависимостей, можно унаследовать библиотечный класс и добавить к нему нужные функции, если библиотечный класс имеет состоянии, то нужно использовать Extension методы, либо делегирование