Конструктор нужно заменить фабричным методом, если не хватает возможностей самого конструктора, например нужно создавать экземпляры разных типов имеющих общего наследника, такое бывает, в проценссе рефакторинга Replace Type Code Subclasses, когда мне нужно создать подклассы, а конструктора не хватает, поскольку он ничего не возвращает, а только создает объект
public enum EmployeeType
{
Fix,
Hourly
}
public class Employee
{
public double SalaryByHour { get; protected set; }
public double FixedSalary { get; protected set; }
private EmployeeType Type { get; }
public Employee(EmployeeType type)
{
Type = type;
}
}
После проведения Replace Type Codee Subsclassess:
public class Employee
{
public static Employee Create(EmployeeType type)
{
switch (type)
{
case EmployeeType.Fix:
return new FixedEmployee();
case EmployeeType.Hourly:
return new HourlyEmployee();
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
}
public class FixedEmployee : Employee
{
public double FixedSalary { get; protected set; }
}
public class HourlyEmployee : Employee
{
public double SalaryByHour { get; protected set; }
}
Вызовы конструтора Employee я заменил на вызов фабричного метода у всех клиентов, тем самым не пришлось сразу рефакторить много клиенткого кода, далее нужно пройтись по всем вызовам фабричного метода и решить сотрудника какого типа нужно создавать.
Также интересная особенность статических фабричных методов в том, что они имеют доступ к приватным полям типа: public class FileLogger : ILogger { private readonly string _path;
public static FileLogger FileLoggerCreate(string path)
{
return new FileLogger(path);
}
private FileLogger(string path)
{
_path = path;
}
public void Log(string text)
{
Console.WriteLine(text);
}
}
Это полезно когда нужен контроль за созданием экземпляров
Принцип в уменьшении количества приведений вниз в коде, и в простосте пользования клиентов, суть в том, что если какой-то метод декларирует более широкий тип чем возвращает на самом деле и клиенты знают об этом, то они будут делать приведение сами:
public static FileLogger FileLoggerCreate(string path)
{
if (IsWinPath(path))
return new WinFileLogger(path);
if (IsLinuxPath(path))
return new LinuxFileLogger(path);
throw new NotImplementedException();
}
public static FileLogger GetWinLogger(string path)
{
return FileLoggerCreate(path);
}
Например вот в таком случае все клиенты метода GetWinLogger будут вынуждены сами делать приведение
FileLogger.GetWinLogger("path") as WinFileLogger
Вместо этого можно сделать приведение в самом методе GetWinLogger:
public static FileLogger GetWinLogger(string path)
{
return FileLoggerCreate(path) as WinFileLogger ;
}
Тем самым облегчив жизнь клиентам и инкапсуривовав этот участок кода, что дает возможность в будущем его аккуратно отрефакторить
Такой рефакторинг часто приходится использовать при разработке домена, если при проектировании были допущены неточности, например, в какой-то метод невозможно попасть находясь в определенном состоянии(не проведен рефакторинг Replace Type Code State/Factory Method), тогда можно поставить guard на вход в метод, чтобы ясно дать понять назначение этого метода, это поможет при дальнейшем рефакторинге, также генерация исключительных ситуаций в конструкторе полезна, поскольку определяет контракт класса, но выкидывать Exception'ы рекомендуется только в домене, слой приложения должен обрабатывать возможные исключительные ситуации
Это рефакторинг для слоя приложения, возможен в случае, когда я поставил try catch, поскольку вызов определенного кода иногда падает, и нет времени разбираться когда, но, когда время появляется, нужно найти место, где код падает, и вернуть сообщение об ошибке, сообщение об ошибке лучше заключить в тип Result